aemeathcli 1.0.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/README.md +607 -0
- package/dist/App-P4MYD4QY.js +2719 -0
- package/dist/App-P4MYD4QY.js.map +1 -0
- package/dist/api-key-fallback-YQQBOQIL.js +11 -0
- package/dist/api-key-fallback-YQQBOQIL.js.map +1 -0
- package/dist/chunk-4IJD72YB.js +184 -0
- package/dist/chunk-4IJD72YB.js.map +1 -0
- package/dist/chunk-6PDJ45T4.js +325 -0
- package/dist/chunk-6PDJ45T4.js.map +1 -0
- package/dist/chunk-CARHU3DO.js +562 -0
- package/dist/chunk-CARHU3DO.js.map +1 -0
- package/dist/chunk-CGEV3ARR.js +80 -0
- package/dist/chunk-CGEV3ARR.js.map +1 -0
- package/dist/chunk-CS5X3BWX.js +27 -0
- package/dist/chunk-CS5X3BWX.js.map +1 -0
- package/dist/chunk-CYQNBB25.js +44 -0
- package/dist/chunk-CYQNBB25.js.map +1 -0
- package/dist/chunk-DAHGLHNR.js +657 -0
- package/dist/chunk-DAHGLHNR.js.map +1 -0
- package/dist/chunk-H66O5Z2V.js +305 -0
- package/dist/chunk-H66O5Z2V.js.map +1 -0
- package/dist/chunk-HCIHOHLX.js +322 -0
- package/dist/chunk-HCIHOHLX.js.map +1 -0
- package/dist/chunk-HMJRPNPZ.js +1031 -0
- package/dist/chunk-HMJRPNPZ.js.map +1 -0
- package/dist/chunk-I5PZ4JTS.js +119 -0
- package/dist/chunk-I5PZ4JTS.js.map +1 -0
- package/dist/chunk-IYW62KKR.js +255 -0
- package/dist/chunk-IYW62KKR.js.map +1 -0
- package/dist/chunk-JAXXTYID.js +51 -0
- package/dist/chunk-JAXXTYID.js.map +1 -0
- package/dist/chunk-LSOYPSAT.js +183 -0
- package/dist/chunk-LSOYPSAT.js.map +1 -0
- package/dist/chunk-MFBHNWGV.js +416 -0
- package/dist/chunk-MFBHNWGV.js.map +1 -0
- package/dist/chunk-MXZSI3AY.js +311 -0
- package/dist/chunk-MXZSI3AY.js.map +1 -0
- package/dist/chunk-NBR3GHMT.js +72 -0
- package/dist/chunk-NBR3GHMT.js.map +1 -0
- package/dist/chunk-O3ZF22SW.js +246 -0
- package/dist/chunk-O3ZF22SW.js.map +1 -0
- package/dist/chunk-SUSJPZU2.js +181 -0
- package/dist/chunk-SUSJPZU2.js.map +1 -0
- package/dist/chunk-TEVZS4FA.js +310 -0
- package/dist/chunk-TEVZS4FA.js.map +1 -0
- package/dist/chunk-UY2SYSEZ.js +211 -0
- package/dist/chunk-UY2SYSEZ.js.map +1 -0
- package/dist/chunk-WAHVZH7V.js +260 -0
- package/dist/chunk-WAHVZH7V.js.map +1 -0
- package/dist/chunk-WPP3PEDE.js +234 -0
- package/dist/chunk-WPP3PEDE.js.map +1 -0
- package/dist/chunk-Y5XVD2CD.js +1610 -0
- package/dist/chunk-Y5XVD2CD.js.map +1 -0
- package/dist/chunk-YL5XFHR3.js +56 -0
- package/dist/chunk-YL5XFHR3.js.map +1 -0
- package/dist/chunk-ZGOHARPV.js +122 -0
- package/dist/chunk-ZGOHARPV.js.map +1 -0
- package/dist/claude-adapter-QMLFMSP3.js +6 -0
- package/dist/claude-adapter-QMLFMSP3.js.map +1 -0
- package/dist/claude-login-5WELXPKT.js +324 -0
- package/dist/claude-login-5WELXPKT.js.map +1 -0
- package/dist/cli.d.ts +1 -0
- package/dist/cli.js +703 -0
- package/dist/cli.js.map +1 -0
- package/dist/codex-login-7HHLJHBF.js +164 -0
- package/dist/codex-login-7HHLJHBF.js.map +1 -0
- package/dist/config-store-W6FBCQAQ.js +6 -0
- package/dist/config-store-W6FBCQAQ.js.map +1 -0
- package/dist/executor-6RIKIGXK.js +4 -0
- package/dist/executor-6RIKIGXK.js.map +1 -0
- package/dist/gemini-adapter-6JIHZ7WI.js +6 -0
- package/dist/gemini-adapter-6JIHZ7WI.js.map +1 -0
- package/dist/gemini-login-ZZLYC3J6.js +346 -0
- package/dist/gemini-login-ZZLYC3J6.js.map +1 -0
- package/dist/index.d.ts +2210 -0
- package/dist/index.js +1419 -0
- package/dist/index.js.map +1 -0
- package/dist/kimi-adapter-JN4HFFHU.js +6 -0
- package/dist/kimi-adapter-JN4HFFHU.js.map +1 -0
- package/dist/kimi-login-CZPS63NK.js +149 -0
- package/dist/kimi-login-CZPS63NK.js.map +1 -0
- package/dist/native-cli-adapters-OLW3XX57.js +6 -0
- package/dist/native-cli-adapters-OLW3XX57.js.map +1 -0
- package/dist/ollama-adapter-OJQ3FKWK.js +6 -0
- package/dist/ollama-adapter-OJQ3FKWK.js.map +1 -0
- package/dist/openai-adapter-XU46EN7B.js +6 -0
- package/dist/openai-adapter-XU46EN7B.js.map +1 -0
- package/dist/registry-4KD24ZC3.js +6 -0
- package/dist/registry-4KD24ZC3.js.map +1 -0
- package/dist/registry-H7B3AHPQ.js +5 -0
- package/dist/registry-H7B3AHPQ.js.map +1 -0
- package/dist/server-manager-PTGBHCLS.js +5 -0
- package/dist/server-manager-PTGBHCLS.js.map +1 -0
- package/dist/session-manager-ECEEACGY.js +12 -0
- package/dist/session-manager-ECEEACGY.js.map +1 -0
- package/dist/team-manager-HC4XGCFY.js +11 -0
- package/dist/team-manager-HC4XGCFY.js.map +1 -0
- package/dist/tmux-manager-GPYZ3WQH.js +6 -0
- package/dist/tmux-manager-GPYZ3WQH.js.map +1 -0
- package/dist/tools-TSMXMHIF.js +6 -0
- package/dist/tools-TSMXMHIF.js.map +1 -0
- package/package.json +89 -0
|
@@ -0,0 +1,657 @@
|
|
|
1
|
+
import { getEventBus } from './chunk-YL5XFHR3.js';
|
|
2
|
+
import { estimateTokenCount, createTokenUsage, formatCost } from './chunk-CGEV3ARR.js';
|
|
3
|
+
import { isCommandBlocked } from './chunk-CS5X3BWX.js';
|
|
4
|
+
import { SUPPORTED_MODELS } from './chunk-HCIHOHLX.js';
|
|
5
|
+
import { ModelNotFoundError } from './chunk-ZGOHARPV.js';
|
|
6
|
+
import { logger } from './chunk-JAXXTYID.js';
|
|
7
|
+
|
|
8
|
+
// src/core/model-router.ts
|
|
9
|
+
var ModelRouter = class {
|
|
10
|
+
config;
|
|
11
|
+
userOverride;
|
|
12
|
+
constructor(config) {
|
|
13
|
+
this.config = config;
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Set a temporary user override that takes highest priority.
|
|
17
|
+
*/
|
|
18
|
+
setUserOverride(modelId) {
|
|
19
|
+
if (modelId !== void 0) {
|
|
20
|
+
this.validateModel(modelId);
|
|
21
|
+
}
|
|
22
|
+
this.userOverride = modelId;
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Resolve the best model for a given role through the priority pipeline.
|
|
26
|
+
*/
|
|
27
|
+
resolve(role) {
|
|
28
|
+
if (this.userOverride) {
|
|
29
|
+
const info = this.getModelInfo(this.userOverride);
|
|
30
|
+
return {
|
|
31
|
+
modelId: this.userOverride,
|
|
32
|
+
provider: info.provider,
|
|
33
|
+
source: "user_override",
|
|
34
|
+
role
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
if (role) {
|
|
38
|
+
const roleConfig = this.config.roles[role];
|
|
39
|
+
if (roleConfig) {
|
|
40
|
+
if (this.isModelAvailable(roleConfig.primary)) {
|
|
41
|
+
const info = this.getModelInfo(roleConfig.primary);
|
|
42
|
+
return {
|
|
43
|
+
modelId: roleConfig.primary,
|
|
44
|
+
provider: info.provider,
|
|
45
|
+
source: "role_config",
|
|
46
|
+
role
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
for (const fallbackModel of roleConfig.fallback) {
|
|
50
|
+
if (this.isModelAvailable(fallbackModel)) {
|
|
51
|
+
const info = this.getModelInfo(fallbackModel);
|
|
52
|
+
logger.info(
|
|
53
|
+
{ role, primary: roleConfig.primary, fallback: fallbackModel },
|
|
54
|
+
"Using fallback model for role"
|
|
55
|
+
);
|
|
56
|
+
return {
|
|
57
|
+
modelId: fallbackModel,
|
|
58
|
+
provider: info.provider,
|
|
59
|
+
source: "fallback_chain",
|
|
60
|
+
role
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
const defaultModel = this.config.defaultModel;
|
|
67
|
+
if (this.isModelAvailable(defaultModel)) {
|
|
68
|
+
const info = this.getModelInfo(defaultModel);
|
|
69
|
+
return {
|
|
70
|
+
modelId: defaultModel,
|
|
71
|
+
provider: info.provider,
|
|
72
|
+
source: "system_default",
|
|
73
|
+
role
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
const anyAvailable = this.getAvailableModels()[0];
|
|
77
|
+
if (anyAvailable) {
|
|
78
|
+
return {
|
|
79
|
+
modelId: anyAvailable.id,
|
|
80
|
+
provider: anyAvailable.provider,
|
|
81
|
+
source: "system_default",
|
|
82
|
+
role
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
throw new ModelNotFoundError(defaultModel);
|
|
86
|
+
}
|
|
87
|
+
/**
|
|
88
|
+
* Check if a model is available (provider is enabled and model is known).
|
|
89
|
+
*/
|
|
90
|
+
isModelAvailable(modelId) {
|
|
91
|
+
const info = SUPPORTED_MODELS[modelId];
|
|
92
|
+
if (!info) {
|
|
93
|
+
return false;
|
|
94
|
+
}
|
|
95
|
+
return this.config.enabledProviders.includes(info.provider);
|
|
96
|
+
}
|
|
97
|
+
/**
|
|
98
|
+
* Get model info by ID. Throws if not found.
|
|
99
|
+
*/
|
|
100
|
+
getModelInfo(modelId) {
|
|
101
|
+
const info = SUPPORTED_MODELS[modelId];
|
|
102
|
+
if (!info) {
|
|
103
|
+
throw new ModelNotFoundError(modelId);
|
|
104
|
+
}
|
|
105
|
+
return info;
|
|
106
|
+
}
|
|
107
|
+
/**
|
|
108
|
+
* Get all available models (from enabled providers).
|
|
109
|
+
*/
|
|
110
|
+
getAvailableModels() {
|
|
111
|
+
return Object.values(SUPPORTED_MODELS).filter(
|
|
112
|
+
(model) => this.config.enabledProviders.includes(model.provider)
|
|
113
|
+
);
|
|
114
|
+
}
|
|
115
|
+
/**
|
|
116
|
+
* List models recommended for a specific role.
|
|
117
|
+
*/
|
|
118
|
+
getModelsForRole(role) {
|
|
119
|
+
return this.getAvailableModels().filter(
|
|
120
|
+
(model) => model.supportedRoles.includes(role)
|
|
121
|
+
);
|
|
122
|
+
}
|
|
123
|
+
/**
|
|
124
|
+
* Validate that a model ID exists. Throws ModelNotFoundError if not.
|
|
125
|
+
*/
|
|
126
|
+
validateModel(modelId) {
|
|
127
|
+
if (!SUPPORTED_MODELS[modelId]) {
|
|
128
|
+
throw new ModelNotFoundError(modelId);
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
};
|
|
132
|
+
function createModelRouter(config) {
|
|
133
|
+
const enabledProviders = Object.entries(config.providers).filter(([, providerConfig]) => providerConfig?.enabled).map(([name]) => name);
|
|
134
|
+
return new ModelRouter({
|
|
135
|
+
defaultModel: config.defaultModel,
|
|
136
|
+
roles: config.roles,
|
|
137
|
+
enabledProviders
|
|
138
|
+
});
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
// src/core/context-manager.ts
|
|
142
|
+
var CONTEXT_BUDGET_RATIO = 0.85;
|
|
143
|
+
var SYSTEM_PROMPT_RESERVE = 4e3;
|
|
144
|
+
var ContextManager = class {
|
|
145
|
+
maxTokens;
|
|
146
|
+
budgetTokens;
|
|
147
|
+
fileContext = /* @__PURE__ */ new Map();
|
|
148
|
+
currentTokenCount = 0;
|
|
149
|
+
constructor(modelInfo) {
|
|
150
|
+
this.maxTokens = modelInfo.contextWindow;
|
|
151
|
+
this.budgetTokens = Math.floor(this.maxTokens * CONTEXT_BUDGET_RATIO);
|
|
152
|
+
}
|
|
153
|
+
/**
|
|
154
|
+
* Get the available token budget for new content.
|
|
155
|
+
*/
|
|
156
|
+
getAvailableBudget() {
|
|
157
|
+
return Math.max(0, this.budgetTokens - this.currentTokenCount - SYSTEM_PROMPT_RESERVE);
|
|
158
|
+
}
|
|
159
|
+
/**
|
|
160
|
+
* Get total context usage.
|
|
161
|
+
*/
|
|
162
|
+
getUsage() {
|
|
163
|
+
return {
|
|
164
|
+
used: this.currentTokenCount,
|
|
165
|
+
budget: this.budgetTokens,
|
|
166
|
+
max: this.maxTokens,
|
|
167
|
+
percentage: Math.round(this.currentTokenCount / this.budgetTokens * 100)
|
|
168
|
+
};
|
|
169
|
+
}
|
|
170
|
+
/**
|
|
171
|
+
* Trim messages to fit within the context window.
|
|
172
|
+
* Preserves system prompt and most recent messages.
|
|
173
|
+
*/
|
|
174
|
+
trimMessages(messages, systemPrompt) {
|
|
175
|
+
const systemTokens = systemPrompt ? estimateTokenCount(systemPrompt) : 0;
|
|
176
|
+
const availableTokens = this.budgetTokens - systemTokens - SYSTEM_PROMPT_RESERVE;
|
|
177
|
+
if (availableTokens <= 0) {
|
|
178
|
+
logger.warn("System prompt alone exceeds context budget");
|
|
179
|
+
return [];
|
|
180
|
+
}
|
|
181
|
+
const result = [];
|
|
182
|
+
let usedTokens = 0;
|
|
183
|
+
for (let i = messages.length - 1; i >= 0; i--) {
|
|
184
|
+
const msg = messages[i];
|
|
185
|
+
if (!msg) {
|
|
186
|
+
continue;
|
|
187
|
+
}
|
|
188
|
+
const msgTokens = estimateTokenCount(msg.content);
|
|
189
|
+
if (usedTokens + msgTokens > availableTokens) {
|
|
190
|
+
break;
|
|
191
|
+
}
|
|
192
|
+
result.unshift(msg);
|
|
193
|
+
usedTokens += msgTokens;
|
|
194
|
+
}
|
|
195
|
+
this.currentTokenCount = usedTokens + systemTokens;
|
|
196
|
+
if (result.length < messages.length) {
|
|
197
|
+
logger.info(
|
|
198
|
+
{
|
|
199
|
+
original: messages.length,
|
|
200
|
+
trimmed: result.length,
|
|
201
|
+
droppedMessages: messages.length - result.length
|
|
202
|
+
},
|
|
203
|
+
"Trimmed conversation to fit context window"
|
|
204
|
+
);
|
|
205
|
+
}
|
|
206
|
+
return result;
|
|
207
|
+
}
|
|
208
|
+
/**
|
|
209
|
+
* Track a file being added to context.
|
|
210
|
+
*/
|
|
211
|
+
addFileContext(filePath, content) {
|
|
212
|
+
const tokenCount = estimateTokenCount(content);
|
|
213
|
+
this.fileContext.set(filePath, {
|
|
214
|
+
filePath,
|
|
215
|
+
tokenCount,
|
|
216
|
+
lastAccessedAt: Date.now()
|
|
217
|
+
});
|
|
218
|
+
this.currentTokenCount += tokenCount;
|
|
219
|
+
}
|
|
220
|
+
/**
|
|
221
|
+
* Touch a file (update last accessed time).
|
|
222
|
+
*/
|
|
223
|
+
touchFile(filePath) {
|
|
224
|
+
const entry = this.fileContext.get(filePath);
|
|
225
|
+
if (entry) {
|
|
226
|
+
entry.lastAccessedAt = Date.now();
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
/**
|
|
230
|
+
* Remove a file from context.
|
|
231
|
+
*/
|
|
232
|
+
removeFileContext(filePath) {
|
|
233
|
+
const entry = this.fileContext.get(filePath);
|
|
234
|
+
if (entry) {
|
|
235
|
+
this.currentTokenCount -= entry.tokenCount;
|
|
236
|
+
this.fileContext.delete(filePath);
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
/**
|
|
240
|
+
* Evict least-recently-used files to free space.
|
|
241
|
+
*/
|
|
242
|
+
evictLRU(tokensNeeded) {
|
|
243
|
+
const evicted = [];
|
|
244
|
+
const sorted = [...this.fileContext.entries()].sort(
|
|
245
|
+
([, a], [, b]) => a.lastAccessedAt - b.lastAccessedAt
|
|
246
|
+
);
|
|
247
|
+
let freedTokens = 0;
|
|
248
|
+
for (const [filePath, entry] of sorted) {
|
|
249
|
+
if (freedTokens >= tokensNeeded) {
|
|
250
|
+
break;
|
|
251
|
+
}
|
|
252
|
+
this.removeFileContext(filePath);
|
|
253
|
+
freedTokens += entry.tokenCount;
|
|
254
|
+
evicted.push(filePath);
|
|
255
|
+
}
|
|
256
|
+
if (evicted.length > 0) {
|
|
257
|
+
logger.info(
|
|
258
|
+
{ evicted, freedTokens },
|
|
259
|
+
"Evicted files from context to free space"
|
|
260
|
+
);
|
|
261
|
+
}
|
|
262
|
+
return evicted;
|
|
263
|
+
}
|
|
264
|
+
/**
|
|
265
|
+
* Get all tracked files.
|
|
266
|
+
*/
|
|
267
|
+
getTrackedFiles() {
|
|
268
|
+
return [...this.fileContext.values()];
|
|
269
|
+
}
|
|
270
|
+
/**
|
|
271
|
+
* Reset context tracking (for model switch).
|
|
272
|
+
*/
|
|
273
|
+
reset() {
|
|
274
|
+
this.fileContext.clear();
|
|
275
|
+
this.currentTokenCount = 0;
|
|
276
|
+
}
|
|
277
|
+
};
|
|
278
|
+
|
|
279
|
+
// src/core/cost-tracker.ts
|
|
280
|
+
var CostTracker = class {
|
|
281
|
+
entries = [];
|
|
282
|
+
budgetConfig;
|
|
283
|
+
warningEmitted = false;
|
|
284
|
+
constructor(budgetConfig) {
|
|
285
|
+
this.budgetConfig = budgetConfig;
|
|
286
|
+
}
|
|
287
|
+
/**
|
|
288
|
+
* Record a cost entry from a model response.
|
|
289
|
+
*/
|
|
290
|
+
record(provider, model, inputTokens, outputTokens, role) {
|
|
291
|
+
const usage = createTokenUsage(model, inputTokens, outputTokens);
|
|
292
|
+
const entry = {
|
|
293
|
+
provider,
|
|
294
|
+
model,
|
|
295
|
+
role,
|
|
296
|
+
usage,
|
|
297
|
+
timestamp: /* @__PURE__ */ new Date()
|
|
298
|
+
};
|
|
299
|
+
this.entries.push(entry);
|
|
300
|
+
const total = this.getSessionTotal();
|
|
301
|
+
const eventBus = getEventBus();
|
|
302
|
+
eventBus.emit("cost:updated", {
|
|
303
|
+
total,
|
|
304
|
+
provider,
|
|
305
|
+
delta: usage.costUsd
|
|
306
|
+
});
|
|
307
|
+
if (total >= this.budgetConfig.budgetWarning && !this.warningEmitted) {
|
|
308
|
+
this.warningEmitted = true;
|
|
309
|
+
eventBus.emit("cost:warning", {
|
|
310
|
+
current: total,
|
|
311
|
+
limit: this.budgetConfig.budgetWarning
|
|
312
|
+
});
|
|
313
|
+
logger.warn(
|
|
314
|
+
{ current: formatCost(total), warning: formatCost(this.budgetConfig.budgetWarning) },
|
|
315
|
+
"Budget warning threshold reached"
|
|
316
|
+
);
|
|
317
|
+
}
|
|
318
|
+
if (total >= this.budgetConfig.budgetHardStop) {
|
|
319
|
+
eventBus.emit("cost:exceeded", {
|
|
320
|
+
current: total,
|
|
321
|
+
limit: this.budgetConfig.budgetHardStop
|
|
322
|
+
});
|
|
323
|
+
logger.error(
|
|
324
|
+
{ current: formatCost(total), limit: formatCost(this.budgetConfig.budgetHardStop) },
|
|
325
|
+
"Budget hard stop reached"
|
|
326
|
+
);
|
|
327
|
+
}
|
|
328
|
+
return usage;
|
|
329
|
+
}
|
|
330
|
+
/**
|
|
331
|
+
* Get total session cost.
|
|
332
|
+
*/
|
|
333
|
+
getSessionTotal() {
|
|
334
|
+
return this.entries.reduce((sum, entry) => sum + entry.usage.costUsd, 0);
|
|
335
|
+
}
|
|
336
|
+
/**
|
|
337
|
+
* Get total token counts.
|
|
338
|
+
*/
|
|
339
|
+
getSessionTokens() {
|
|
340
|
+
const input = this.entries.reduce((sum, e) => sum + e.usage.inputTokens, 0);
|
|
341
|
+
const output = this.entries.reduce((sum, e) => sum + e.usage.outputTokens, 0);
|
|
342
|
+
return { input, output, total: input + output };
|
|
343
|
+
}
|
|
344
|
+
/**
|
|
345
|
+
* Get cost breakdown by provider, model, and role.
|
|
346
|
+
*/
|
|
347
|
+
getBreakdown() {
|
|
348
|
+
const byProvider = {};
|
|
349
|
+
const byModel = {};
|
|
350
|
+
const byRole = {};
|
|
351
|
+
for (const entry of this.entries) {
|
|
352
|
+
byProvider[entry.provider] = (byProvider[entry.provider] ?? 0) + entry.usage.costUsd;
|
|
353
|
+
byModel[entry.model] = (byModel[entry.model] ?? 0) + entry.usage.costUsd;
|
|
354
|
+
if (entry.role) {
|
|
355
|
+
byRole[entry.role] = (byRole[entry.role] ?? 0) + entry.usage.costUsd;
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
return { byProvider, byModel, byRole };
|
|
359
|
+
}
|
|
360
|
+
/**
|
|
361
|
+
* Check if budget hard stop has been exceeded.
|
|
362
|
+
*/
|
|
363
|
+
isBudgetExceeded() {
|
|
364
|
+
return this.getSessionTotal() >= this.budgetConfig.budgetHardStop;
|
|
365
|
+
}
|
|
366
|
+
/**
|
|
367
|
+
* Get formatted session summary.
|
|
368
|
+
*/
|
|
369
|
+
getSummary() {
|
|
370
|
+
const total = this.getSessionTotal();
|
|
371
|
+
const tokens = this.getSessionTokens();
|
|
372
|
+
return `${formatCost(total)} (${tokens.total.toLocaleString()} tokens)`;
|
|
373
|
+
}
|
|
374
|
+
/**
|
|
375
|
+
* Get all cost entries (for export).
|
|
376
|
+
*/
|
|
377
|
+
getEntries() {
|
|
378
|
+
return this.entries;
|
|
379
|
+
}
|
|
380
|
+
/**
|
|
381
|
+
* Reset cost tracking for a new session.
|
|
382
|
+
*/
|
|
383
|
+
reset() {
|
|
384
|
+
this.entries.length = 0;
|
|
385
|
+
this.warningEmitted = false;
|
|
386
|
+
}
|
|
387
|
+
};
|
|
388
|
+
|
|
389
|
+
// src/core/permission-manager.ts
|
|
390
|
+
var DANGEROUS_COMMANDS = [
|
|
391
|
+
"rm -rf",
|
|
392
|
+
"git push --force",
|
|
393
|
+
"git reset --hard",
|
|
394
|
+
"git checkout .",
|
|
395
|
+
"git clean -f",
|
|
396
|
+
"git branch -D",
|
|
397
|
+
"drop table",
|
|
398
|
+
"drop database",
|
|
399
|
+
"truncate",
|
|
400
|
+
"format c:",
|
|
401
|
+
"del /f /s /q"
|
|
402
|
+
];
|
|
403
|
+
var PermissionManager = class {
|
|
404
|
+
mode;
|
|
405
|
+
allowedPaths;
|
|
406
|
+
blockedCommands;
|
|
407
|
+
approvedOperations = /* @__PURE__ */ new Set();
|
|
408
|
+
constructor(mode, allowedPaths, blockedCommands) {
|
|
409
|
+
this.mode = mode;
|
|
410
|
+
this.allowedPaths = allowedPaths;
|
|
411
|
+
this.blockedCommands = blockedCommands;
|
|
412
|
+
}
|
|
413
|
+
/**
|
|
414
|
+
* Check if an operation is permitted.
|
|
415
|
+
*/
|
|
416
|
+
check(request) {
|
|
417
|
+
if (request.command && this.isDangerousCommand(request.command)) {
|
|
418
|
+
return {
|
|
419
|
+
allowed: false,
|
|
420
|
+
reason: `Dangerous command detected: "${request.command}"`,
|
|
421
|
+
requiresUserApproval: true
|
|
422
|
+
};
|
|
423
|
+
}
|
|
424
|
+
if (request.command && isCommandBlocked(request.command, this.blockedCommands)) {
|
|
425
|
+
return {
|
|
426
|
+
allowed: false,
|
|
427
|
+
reason: `Command is on the blocked list`,
|
|
428
|
+
requiresUserApproval: true
|
|
429
|
+
};
|
|
430
|
+
}
|
|
431
|
+
const opKey = this.getOperationKey(request);
|
|
432
|
+
if (this.approvedOperations.has(opKey)) {
|
|
433
|
+
return { allowed: true, requiresUserApproval: false };
|
|
434
|
+
}
|
|
435
|
+
switch (this.mode) {
|
|
436
|
+
case "permissive":
|
|
437
|
+
return { allowed: true, requiresUserApproval: false };
|
|
438
|
+
case "standard":
|
|
439
|
+
return this.checkStandardMode(request);
|
|
440
|
+
case "strict":
|
|
441
|
+
return this.checkStrictMode(request);
|
|
442
|
+
}
|
|
443
|
+
}
|
|
444
|
+
/**
|
|
445
|
+
* Record that the user has approved an operation.
|
|
446
|
+
*/
|
|
447
|
+
approve(request) {
|
|
448
|
+
const opKey = this.getOperationKey(request);
|
|
449
|
+
this.approvedOperations.add(opKey);
|
|
450
|
+
logger.info({ operation: opKey }, "Operation approved by user");
|
|
451
|
+
}
|
|
452
|
+
/**
|
|
453
|
+
* Update permission mode.
|
|
454
|
+
*/
|
|
455
|
+
setMode(mode) {
|
|
456
|
+
this.mode = mode;
|
|
457
|
+
this.approvedOperations.clear();
|
|
458
|
+
logger.info({ mode }, "Permission mode changed");
|
|
459
|
+
}
|
|
460
|
+
/**
|
|
461
|
+
* Get current mode.
|
|
462
|
+
*/
|
|
463
|
+
getMode() {
|
|
464
|
+
return this.mode;
|
|
465
|
+
}
|
|
466
|
+
checkStandardMode(request) {
|
|
467
|
+
if (this.isReadOperation(request)) {
|
|
468
|
+
return { allowed: true, requiresUserApproval: false };
|
|
469
|
+
}
|
|
470
|
+
return {
|
|
471
|
+
allowed: false,
|
|
472
|
+
reason: `${request.operation} requires approval in standard mode`,
|
|
473
|
+
requiresUserApproval: true
|
|
474
|
+
};
|
|
475
|
+
}
|
|
476
|
+
checkStrictMode(request) {
|
|
477
|
+
return {
|
|
478
|
+
allowed: false,
|
|
479
|
+
reason: `${request.operation} requires approval in strict mode`,
|
|
480
|
+
requiresUserApproval: true
|
|
481
|
+
};
|
|
482
|
+
}
|
|
483
|
+
isReadOperation(request) {
|
|
484
|
+
const readTools = ["read", "glob", "grep", "web-search", "web-fetch"];
|
|
485
|
+
return readTools.includes(request.toolName);
|
|
486
|
+
}
|
|
487
|
+
isDangerousCommand(command) {
|
|
488
|
+
const lower = command.toLowerCase().trim();
|
|
489
|
+
return DANGEROUS_COMMANDS.some((dangerous) => lower.includes(dangerous));
|
|
490
|
+
}
|
|
491
|
+
getOperationKey(request) {
|
|
492
|
+
return `${request.toolName}:${request.operation}:${request.resource ?? ""}`;
|
|
493
|
+
}
|
|
494
|
+
};
|
|
495
|
+
|
|
496
|
+
// src/core/task-orchestrator.ts
|
|
497
|
+
var nextTaskId = 1;
|
|
498
|
+
function generateTaskId() {
|
|
499
|
+
return String(nextTaskId++);
|
|
500
|
+
}
|
|
501
|
+
var TaskOrchestrator = class {
|
|
502
|
+
tasks = /* @__PURE__ */ new Map();
|
|
503
|
+
/**
|
|
504
|
+
* Create a new task.
|
|
505
|
+
*/
|
|
506
|
+
createTask(subject, description, options) {
|
|
507
|
+
const id = generateTaskId();
|
|
508
|
+
const now = /* @__PURE__ */ new Date();
|
|
509
|
+
const task = {
|
|
510
|
+
id,
|
|
511
|
+
subject,
|
|
512
|
+
description,
|
|
513
|
+
status: "pending",
|
|
514
|
+
owner: options?.owner,
|
|
515
|
+
model: options?.model,
|
|
516
|
+
role: options?.role,
|
|
517
|
+
blocks: [],
|
|
518
|
+
blockedBy: options?.blockedBy ? [...options.blockedBy] : [],
|
|
519
|
+
createdAt: now,
|
|
520
|
+
updatedAt: now
|
|
521
|
+
};
|
|
522
|
+
this.tasks.set(id, task);
|
|
523
|
+
if (options?.blockedBy) {
|
|
524
|
+
for (const blockerId of options.blockedBy) {
|
|
525
|
+
const blocker = this.tasks.get(blockerId);
|
|
526
|
+
if (blocker && !blocker.blocks.includes(id)) {
|
|
527
|
+
blocker.blocks.push(id);
|
|
528
|
+
}
|
|
529
|
+
}
|
|
530
|
+
}
|
|
531
|
+
getEventBus().emit("task:created", { taskId: id, subject });
|
|
532
|
+
logger.info({ taskId: id, subject }, "Task created");
|
|
533
|
+
return task;
|
|
534
|
+
}
|
|
535
|
+
/**
|
|
536
|
+
* Update task status.
|
|
537
|
+
*/
|
|
538
|
+
updateStatus(taskId, status) {
|
|
539
|
+
const task = this.getTask(taskId);
|
|
540
|
+
task.status = status;
|
|
541
|
+
task.updatedAt = /* @__PURE__ */ new Date();
|
|
542
|
+
getEventBus().emit("task:updated", { taskId, status });
|
|
543
|
+
if (status === "completed") {
|
|
544
|
+
getEventBus().emit("task:completed", { taskId });
|
|
545
|
+
this.resolveBlockedTasks(taskId);
|
|
546
|
+
}
|
|
547
|
+
logger.info({ taskId, status }, "Task status updated");
|
|
548
|
+
}
|
|
549
|
+
/**
|
|
550
|
+
* Assign a task to an agent.
|
|
551
|
+
*/
|
|
552
|
+
assignTask(taskId, owner, model) {
|
|
553
|
+
const task = this.getTask(taskId);
|
|
554
|
+
task.owner = owner;
|
|
555
|
+
if (model) {
|
|
556
|
+
task.model = model;
|
|
557
|
+
}
|
|
558
|
+
task.updatedAt = /* @__PURE__ */ new Date();
|
|
559
|
+
logger.info({ taskId, owner, model }, "Task assigned");
|
|
560
|
+
}
|
|
561
|
+
/**
|
|
562
|
+
* Get a task by ID. Throws if not found.
|
|
563
|
+
*/
|
|
564
|
+
getTask(taskId) {
|
|
565
|
+
const task = this.tasks.get(taskId);
|
|
566
|
+
if (!task) {
|
|
567
|
+
throw new Error(`Task not found: ${taskId}`);
|
|
568
|
+
}
|
|
569
|
+
return task;
|
|
570
|
+
}
|
|
571
|
+
/**
|
|
572
|
+
* Get all tasks.
|
|
573
|
+
*/
|
|
574
|
+
getAllTasks() {
|
|
575
|
+
return [...this.tasks.values()];
|
|
576
|
+
}
|
|
577
|
+
/**
|
|
578
|
+
* Get tasks by status.
|
|
579
|
+
*/
|
|
580
|
+
getTasksByStatus(status) {
|
|
581
|
+
return [...this.tasks.values()].filter((t) => t.status === status);
|
|
582
|
+
}
|
|
583
|
+
/**
|
|
584
|
+
* Get tasks assigned to an agent.
|
|
585
|
+
*/
|
|
586
|
+
getTasksByOwner(owner) {
|
|
587
|
+
return [...this.tasks.values()].filter((t) => t.owner === owner);
|
|
588
|
+
}
|
|
589
|
+
/**
|
|
590
|
+
* Get tasks that are ready to be worked on (pending, not blocked).
|
|
591
|
+
*/
|
|
592
|
+
getAvailableTasks() {
|
|
593
|
+
return [...this.tasks.values()].filter(
|
|
594
|
+
(t) => t.status === "pending" && !t.owner && t.blockedBy.every((blockerId) => {
|
|
595
|
+
const blocker = this.tasks.get(blockerId);
|
|
596
|
+
return blocker?.status === "completed";
|
|
597
|
+
})
|
|
598
|
+
);
|
|
599
|
+
}
|
|
600
|
+
/**
|
|
601
|
+
* Check if all tasks are completed.
|
|
602
|
+
*/
|
|
603
|
+
isAllComplete() {
|
|
604
|
+
return [...this.tasks.values()].every((t) => t.status === "completed");
|
|
605
|
+
}
|
|
606
|
+
/**
|
|
607
|
+
* Get progress summary.
|
|
608
|
+
*/
|
|
609
|
+
getProgress() {
|
|
610
|
+
const tasks = [...this.tasks.values()];
|
|
611
|
+
return {
|
|
612
|
+
total: tasks.length,
|
|
613
|
+
completed: tasks.filter((t) => t.status === "completed").length,
|
|
614
|
+
inProgress: tasks.filter((t) => t.status === "in_progress").length,
|
|
615
|
+
pending: tasks.filter((t) => t.status === "pending").length,
|
|
616
|
+
blocked: tasks.filter((t) => t.status === "blocked").length
|
|
617
|
+
};
|
|
618
|
+
}
|
|
619
|
+
/**
|
|
620
|
+
* Delete a task.
|
|
621
|
+
*/
|
|
622
|
+
deleteTask(taskId) {
|
|
623
|
+
const task = this.tasks.get(taskId);
|
|
624
|
+
if (task) {
|
|
625
|
+
for (const [, otherTask] of this.tasks) {
|
|
626
|
+
otherTask.blockedBy = otherTask.blockedBy.filter((id) => id !== taskId);
|
|
627
|
+
otherTask.blocks = otherTask.blocks.filter((id) => id !== taskId);
|
|
628
|
+
}
|
|
629
|
+
this.tasks.delete(taskId);
|
|
630
|
+
}
|
|
631
|
+
}
|
|
632
|
+
/**
|
|
633
|
+
* When a task completes, check if any blocked tasks can now proceed.
|
|
634
|
+
*/
|
|
635
|
+
resolveBlockedTasks(completedTaskId) {
|
|
636
|
+
for (const [, task] of this.tasks) {
|
|
637
|
+
if (task.status === "blocked" || task.status === "pending") {
|
|
638
|
+
const allDepsComplete = task.blockedBy.every((depId) => {
|
|
639
|
+
const dep = this.tasks.get(depId);
|
|
640
|
+
return dep?.status === "completed";
|
|
641
|
+
});
|
|
642
|
+
if (allDepsComplete && task.status === "blocked") {
|
|
643
|
+
task.status = "pending";
|
|
644
|
+
task.updatedAt = /* @__PURE__ */ new Date();
|
|
645
|
+
logger.info(
|
|
646
|
+
{ taskId: task.id, unblockedBy: completedTaskId },
|
|
647
|
+
"Task unblocked"
|
|
648
|
+
);
|
|
649
|
+
}
|
|
650
|
+
}
|
|
651
|
+
}
|
|
652
|
+
}
|
|
653
|
+
};
|
|
654
|
+
|
|
655
|
+
export { ContextManager, CostTracker, ModelRouter, PermissionManager, TaskOrchestrator, createModelRouter };
|
|
656
|
+
//# sourceMappingURL=chunk-DAHGLHNR.js.map
|
|
657
|
+
//# sourceMappingURL=chunk-DAHGLHNR.js.map
|