@travisennis/acai 0.0.13 → 0.0.14
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 +2 -0
- package/dist/agent/index.d.ts.map +1 -1
- package/dist/agent/index.js +89 -0
- package/dist/config/index.js +1 -1
- package/dist/models/opencode-go-provider.d.ts +3 -1
- package/dist/models/opencode-go-provider.d.ts.map +1 -1
- package/dist/models/opencode-go-provider.js +28 -2
- package/dist/models/providers.d.ts +1 -1
- package/dist/models/providers.d.ts.map +1 -1
- package/dist/sessions/manager.d.ts +41 -0
- package/dist/sessions/manager.d.ts.map +1 -1
- package/dist/sessions/manager.js +53 -0
- package/dist/sessions/summary.d.ts.map +1 -1
- package/dist/sessions/summary.js +18 -0
- package/dist/utils/bash.d.ts.map +1 -1
- package/dist/utils/bash.js +52 -17
- package/dist/utils/command-protection.d.ts +0 -4
- package/dist/utils/command-protection.d.ts.map +1 -1
- package/dist/utils/command-protection.js +171 -67
- package/package.json +16 -16
package/README.md
CHANGED
|
@@ -111,6 +111,8 @@ Reference files directly with `@filename`, directories with `@dirname`, or run s
|
|
|
111
111
|
- [Dynamic Tools](docs/dynamic-tools.md) - Creating custom tools to extend acai (supports bash, python, and other languages; Amp-compatible text schema format; `/tools make` scaffolding command)
|
|
112
112
|
- [Architecture](ARCHITECTURE.md) - Internal architecture and flow diagrams
|
|
113
113
|
- [Contributing](CONTRIBUTING.md) - Development setup, scripts, and code style
|
|
114
|
+
- [Agent Instructions](AGENTS.md) - Routing guide for AI agents working in this repo
|
|
115
|
+
- [Agent Guardrails](docs/guardrails/) - Focused compatibility, security, testing, and documentation rules
|
|
114
116
|
|
|
115
117
|
## Acknowledgments
|
|
116
118
|
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../source/agent/index.ts"],"names":[],"mappings":"AAgBA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,oBAAoB,CAAC;AAEjD,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,sBAAsB,CAAC;AACzD,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,wBAAwB,CAAC;AAC5D,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,wBAAwB,CAAC;AAC7D,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,sBAAsB,CAAC;AACzD,OAAO,KAAK,EACV,iBAAiB,EACjB,eAAe,EAEhB,MAAM,mBAAmB,CAAC;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../source/agent/index.ts"],"names":[],"mappings":"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"}
|
package/dist/agent/index.js
CHANGED
|
@@ -2,6 +2,42 @@ import { generateText, InvalidToolInputError, NoOutputGeneratedError, NoSuchTool
|
|
|
2
2
|
import { AiConfig } from "../models/ai-config.js";
|
|
3
3
|
import { toAiSdkTools } from "../tools/utils.js";
|
|
4
4
|
import { logger } from "../utils/logger.js";
|
|
5
|
+
/**
|
|
6
|
+
* Whether a stream chunk carries model output (used to mark time-to-first-token).
|
|
7
|
+
*/
|
|
8
|
+
function isContentChunk(chunk) {
|
|
9
|
+
return (chunk.type === "text-delta" ||
|
|
10
|
+
chunk.type === "reasoning-delta" ||
|
|
11
|
+
chunk.type === "tool-call");
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* Rough input-token estimate for the request.start event. Real input token
|
|
15
|
+
* counts are reported on request.end from provider usage.
|
|
16
|
+
*/
|
|
17
|
+
function estimateInputTokens(messages, systemPrompt) {
|
|
18
|
+
const chars = systemPrompt.length + JSON.stringify(messages).length;
|
|
19
|
+
return Math.ceil(chars / 4);
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Selects provider response headers worth keeping for telemetry: request id,
|
|
23
|
+
* rate-limit headers, and retry hints. Header names vary per provider.
|
|
24
|
+
*/
|
|
25
|
+
function extractProviderHeaders(headers) {
|
|
26
|
+
if (!headers) {
|
|
27
|
+
return {};
|
|
28
|
+
}
|
|
29
|
+
const out = {};
|
|
30
|
+
for (const [key, value] of Object.entries(headers)) {
|
|
31
|
+
const lower = key.toLowerCase();
|
|
32
|
+
if (lower === "x-request-id" ||
|
|
33
|
+
lower === "retry-after" ||
|
|
34
|
+
lower.includes("ratelimit") ||
|
|
35
|
+
lower.includes("rate-limit")) {
|
|
36
|
+
out[lower] = value;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
return out;
|
|
40
|
+
}
|
|
5
41
|
export class Agent {
|
|
6
42
|
opts;
|
|
7
43
|
config;
|
|
@@ -55,6 +91,9 @@ export class Agent {
|
|
|
55
91
|
type: "step-start",
|
|
56
92
|
};
|
|
57
93
|
const toolsCalled = new Map();
|
|
94
|
+
const requestId = `${sessionManager.getSessionId()}:${iter}`;
|
|
95
|
+
const requestStart = performance.now();
|
|
96
|
+
let firstTokenAt = null;
|
|
58
97
|
try {
|
|
59
98
|
// Check abort signal again before starting streamText
|
|
60
99
|
if (abortSignal?.aborted) {
|
|
@@ -62,6 +101,15 @@ export class Agent {
|
|
|
62
101
|
}
|
|
63
102
|
const messages = sessionManager.get();
|
|
64
103
|
const aiSdkTools = toAiSdkTools(tools, false);
|
|
104
|
+
logger.info({
|
|
105
|
+
event: "model.request.start",
|
|
106
|
+
requestId,
|
|
107
|
+
sessionId: sessionManager.getSessionId(),
|
|
108
|
+
model: modelConfig.id,
|
|
109
|
+
provider: modelConfig.provider,
|
|
110
|
+
iteration: iter,
|
|
111
|
+
inputTokenEstimate: estimateInputTokens(messages, systemPrompt),
|
|
112
|
+
}, "model.request.start");
|
|
65
113
|
const result = streamText({
|
|
66
114
|
model: langModel,
|
|
67
115
|
maxOutputTokens: aiConfig.maxOutputTokens(),
|
|
@@ -103,6 +151,14 @@ export class Agent {
|
|
|
103
151
|
});
|
|
104
152
|
const pendingToolCalls = [];
|
|
105
153
|
for await (const chunk of result.fullStream) {
|
|
154
|
+
if (firstTokenAt === null && isContentChunk(chunk)) {
|
|
155
|
+
firstTokenAt = performance.now();
|
|
156
|
+
logger.info({
|
|
157
|
+
event: "model.first_token",
|
|
158
|
+
requestId,
|
|
159
|
+
ttftMs: Math.round(firstTokenAt - requestStart),
|
|
160
|
+
}, "model.first_token");
|
|
161
|
+
}
|
|
106
162
|
const event = this.handleStreamChunk(chunk, {
|
|
107
163
|
accumulatedText,
|
|
108
164
|
accumulatedReasoning,
|
|
@@ -116,9 +172,11 @@ export class Agent {
|
|
|
116
172
|
yield event.agentEvent;
|
|
117
173
|
}
|
|
118
174
|
}
|
|
175
|
+
const modelStreamEnd = performance.now();
|
|
119
176
|
// ============================================================
|
|
120
177
|
// PARALLEL TOOL EXECUTION
|
|
121
178
|
// ============================================================
|
|
179
|
+
const toolStart = performance.now();
|
|
122
180
|
const { collectedEvents, parallelToolMessages } = await this.executeToolsInParallel({
|
|
123
181
|
pendingToolCalls,
|
|
124
182
|
abortSignal,
|
|
@@ -126,6 +184,7 @@ export class Agent {
|
|
|
126
184
|
stepToolCalls: thisStepToolCalls,
|
|
127
185
|
stepToolResults: thisStepToolResults,
|
|
128
186
|
});
|
|
187
|
+
const toolEnd = performance.now();
|
|
129
188
|
// Yield all collected events in order
|
|
130
189
|
for (const event of collectedEvents) {
|
|
131
190
|
yield event;
|
|
@@ -143,6 +202,35 @@ export class Agent {
|
|
|
143
202
|
this.updateUsageStats(stepUsage, sessionManager);
|
|
144
203
|
// If finishReason is not tool-calls, break
|
|
145
204
|
const finishReason = await result.finishReason;
|
|
205
|
+
const modelResponseMs = modelStreamEnd - requestStart;
|
|
206
|
+
const toolMs = toolEnd - toolStart;
|
|
207
|
+
const wallClockMs = performance.now() - requestStart;
|
|
208
|
+
const outputTokens = stepUsage.outputTokens ?? 0;
|
|
209
|
+
logger.info({
|
|
210
|
+
event: "model.request.end",
|
|
211
|
+
requestId,
|
|
212
|
+
model: modelConfig.id,
|
|
213
|
+
provider: modelConfig.provider,
|
|
214
|
+
modelResponseMs: Math.round(modelResponseMs),
|
|
215
|
+
ttftMs: firstTokenAt === null
|
|
216
|
+
? null
|
|
217
|
+
: Math.round(firstTokenAt - requestStart),
|
|
218
|
+
inputTokens: stepUsage.inputTokens ?? 0,
|
|
219
|
+
outputTokens,
|
|
220
|
+
outputTokensPerSecond: modelResponseMs > 0
|
|
221
|
+
? Number((outputTokens / (modelResponseMs / 1000)).toFixed(2))
|
|
222
|
+
: 0,
|
|
223
|
+
reasoningTokens: stepUsage.outputTokenDetails.reasoningTokens ?? 0,
|
|
224
|
+
finishReason,
|
|
225
|
+
retryCount: consecutiveErrors,
|
|
226
|
+
providerRequestId: response.id ?? null,
|
|
227
|
+
providerHeaders: extractProviderHeaders(response.headers),
|
|
228
|
+
}, "model.request.end");
|
|
229
|
+
sessionManager.recordTurnTiming({
|
|
230
|
+
wallClockMs,
|
|
231
|
+
modelMs: modelResponseMs,
|
|
232
|
+
toolMs,
|
|
233
|
+
});
|
|
146
234
|
if (finishReason !== "tool-calls") {
|
|
147
235
|
yield {
|
|
148
236
|
type: "agent-stop",
|
|
@@ -157,6 +245,7 @@ export class Agent {
|
|
|
157
245
|
type: "step-stop",
|
|
158
246
|
};
|
|
159
247
|
// continue iterations
|
|
248
|
+
consecutiveErrors = 0;
|
|
160
249
|
iter += 1;
|
|
161
250
|
}
|
|
162
251
|
catch (error) {
|
package/dist/config/index.js
CHANGED
|
@@ -151,7 +151,7 @@ export class ConfigManager {
|
|
|
151
151
|
async _readConfig(configPath) {
|
|
152
152
|
try {
|
|
153
153
|
const data = await fs.readFile(configPath, "utf8");
|
|
154
|
-
return
|
|
154
|
+
return JSON.parse(data);
|
|
155
155
|
}
|
|
156
156
|
catch (error) {
|
|
157
157
|
const code = error.code;
|
|
@@ -4,8 +4,10 @@ declare const opencodeGoModels: {
|
|
|
4
4
|
readonly "deepseek-v4-pro": import("@ai-sdk/provider").LanguageModelV3;
|
|
5
5
|
readonly "glm-5": import("@ai-sdk/provider").LanguageModelV3;
|
|
6
6
|
readonly "glm-5-1": import("@ai-sdk/provider").LanguageModelV3;
|
|
7
|
+
readonly "glm-5-2": import("@ai-sdk/provider").LanguageModelV3;
|
|
7
8
|
readonly "kimi-k2-5": import("@ai-sdk/provider").LanguageModelV3;
|
|
8
9
|
readonly "kimi-k2-6": import("@ai-sdk/provider").LanguageModelV3;
|
|
10
|
+
readonly "kimi-k2-7-code": import("@ai-sdk/provider").LanguageModelV3;
|
|
9
11
|
readonly "minimax-m2-5": import("@ai-sdk/provider").LanguageModelV3;
|
|
10
12
|
readonly "minimax-m2-7": import("@ai-sdk/provider").LanguageModelV3;
|
|
11
13
|
readonly "mimo-v2-5": import("@ai-sdk/provider").LanguageModelV3;
|
|
@@ -19,7 +21,7 @@ type ModelName = `opencode-go:${keyof typeof opencodeGoModels}`;
|
|
|
19
21
|
export declare const opencodeGoModelNames: ModelName[];
|
|
20
22
|
export declare const opencodeGoProvider: {
|
|
21
23
|
"opencode-go": import("@ai-sdk/provider").ProviderV3 & {
|
|
22
|
-
languageModel(modelId: "deepseek-v4-flash" | "deepseek-v4-pro" | "glm-5" | "minimax-m3" | "qwen3.6-plus" | "qwen3.7-plus" | "qwen3.7-max" | "glm-5-1" | "kimi-k2-5" | "kimi-k2-6" | "minimax-m2-5" | "minimax-m2-7" | "mimo-v2-5" | "mimo-v2-5-pro"): import("@ai-sdk/provider").LanguageModelV3;
|
|
24
|
+
languageModel(modelId: "deepseek-v4-flash" | "deepseek-v4-pro" | "glm-5" | "minimax-m3" | "qwen3.6-plus" | "qwen3.7-plus" | "qwen3.7-max" | "glm-5-1" | "glm-5-2" | "kimi-k2-5" | "kimi-k2-6" | "kimi-k2-7-code" | "minimax-m2-5" | "minimax-m2-7" | "mimo-v2-5" | "mimo-v2-5-pro"): import("@ai-sdk/provider").LanguageModelV3;
|
|
23
25
|
embeddingModel(modelId: string): import("@ai-sdk/provider").EmbeddingModelV3;
|
|
24
26
|
imageModel(modelId: string): import("@ai-sdk/provider").ImageModelV3;
|
|
25
27
|
transcriptionModel(modelId: string): import("@ai-sdk/provider").TranscriptionModelV3;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"opencode-go-provider.d.ts","sourceRoot":"","sources":["../../source/models/opencode-go-provider.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,gBAAgB,CAAC;AAepD,QAAA,MAAM,gBAAgB
|
|
1
|
+
{"version":3,"file":"opencode-go-provider.d.ts","sourceRoot":"","sources":["../../source/models/opencode-go-provider.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,gBAAgB,CAAC;AAepD,QAAA,MAAM,gBAAgB;;;;;;;;;;;;;;;;;CAiBZ,CAAC;AAEX,KAAK,SAAS,GAAG,eAAe,MAAM,OAAO,gBAAgB,EAAE,CAAC;AAEhE,eAAO,MAAM,oBAAoB,EAAE,SAAS,EAEC,CAAC;AAE9C,eAAO,MAAM,kBAAkB;;;;;;;;;;CAK9B,CAAC;AAEF,eAAO,MAAM,uBAAuB,EAAE;KACnC,CAAC,IAAI,SAAS,GAAG,aAAa,CAAC,SAAS,CAAC;CAkM3C,CAAC"}
|
|
@@ -18,8 +18,10 @@ const opencodeGoModels = {
|
|
|
18
18
|
"deepseek-v4-pro": completionsClient("deepseek-v4-pro"),
|
|
19
19
|
"glm-5": completionsClient("glm-5"),
|
|
20
20
|
"glm-5-1": completionsClient("glm-5.1"),
|
|
21
|
+
"glm-5-2": completionsClient("glm-5.2"),
|
|
21
22
|
"kimi-k2-5": completionsClient("kimi-k2.5"),
|
|
22
23
|
"kimi-k2-6": completionsClient("kimi-k2.6"),
|
|
24
|
+
"kimi-k2-7-code": completionsClient("kimi-k2.7-code"),
|
|
23
25
|
"minimax-m2-5": messagesClient("minimax-m2.5"),
|
|
24
26
|
"minimax-m2-7": messagesClient("minimax-m2.7"),
|
|
25
27
|
"mimo-v2-5": completionsClient("mimo-v2.5"),
|
|
@@ -85,6 +87,18 @@ export const opencodeGoModelRegistry = {
|
|
|
85
87
|
costPerInputToken: 0.0000014,
|
|
86
88
|
costPerOutputToken: 0.0000044,
|
|
87
89
|
},
|
|
90
|
+
"opencode-go:glm-5-2": {
|
|
91
|
+
id: "opencode-go:glm-5-2",
|
|
92
|
+
provider: "opencode-go",
|
|
93
|
+
contextWindow: 202752,
|
|
94
|
+
maxOutputTokens: 202752,
|
|
95
|
+
defaultTemperature: 0.5,
|
|
96
|
+
promptFormat: "markdown",
|
|
97
|
+
supportsReasoning: true,
|
|
98
|
+
supportsToolCalling: true,
|
|
99
|
+
costPerInputToken: 0.0000014,
|
|
100
|
+
costPerOutputToken: 0.0000044,
|
|
101
|
+
},
|
|
88
102
|
"opencode-go:kimi-k2-5": {
|
|
89
103
|
id: "opencode-go:kimi-k2-5",
|
|
90
104
|
provider: "opencode-go",
|
|
@@ -109,6 +123,18 @@ export const opencodeGoModelRegistry = {
|
|
|
109
123
|
costPerInputToken: 0.00000095,
|
|
110
124
|
costPerOutputToken: 0.000004,
|
|
111
125
|
},
|
|
126
|
+
"opencode-go:kimi-k2-7-code": {
|
|
127
|
+
id: "opencode-go:kimi-k2-7-code",
|
|
128
|
+
provider: "opencode-go",
|
|
129
|
+
contextWindow: 256000,
|
|
130
|
+
maxOutputTokens: 65536,
|
|
131
|
+
defaultTemperature: 1.0,
|
|
132
|
+
promptFormat: "markdown",
|
|
133
|
+
supportsReasoning: true,
|
|
134
|
+
supportsToolCalling: true,
|
|
135
|
+
costPerInputToken: 0.00000095,
|
|
136
|
+
costPerOutputToken: 0.000004,
|
|
137
|
+
},
|
|
112
138
|
"opencode-go:minimax-m2-5": {
|
|
113
139
|
id: "opencode-go:minimax-m2-5",
|
|
114
140
|
provider: "opencode-go",
|
|
@@ -166,8 +192,8 @@ export const opencodeGoModelRegistry = {
|
|
|
166
192
|
promptFormat: "markdown",
|
|
167
193
|
supportsReasoning: true,
|
|
168
194
|
supportsToolCalling: true,
|
|
169
|
-
costPerInputToken:
|
|
170
|
-
costPerOutputToken: 0.
|
|
195
|
+
costPerInputToken: 0.0000003,
|
|
196
|
+
costPerOutputToken: 0.0000012,
|
|
171
197
|
},
|
|
172
198
|
"opencode-go:qwen3.6-plus": {
|
|
173
199
|
id: "opencode-go:qwen3.6-plus",
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
declare const providers: readonly ["anthropic", "openai", "google", "groq", "deepseek", "openrouter", "xai", "opencode", "opencode-go"];
|
|
2
2
|
type ModelProvider = (typeof providers)[number];
|
|
3
|
-
export declare const models: readonly ("anthropic:haiku" | "anthropic:opus" | "anthropic:sonnet" | "deepseek:deepseek-v4-flash" | "deepseek:deepseek-v4-pro" | "openai:gpt-5.1-codex-mini" | "openai:gpt-5.2" | "openai:gpt-5.2-codex" | "opencode-go:deepseek-v4-flash" | "opencode-go:deepseek-v4-pro" | "opencode-go:glm-5" | "opencode-go:minimax-m3" | "opencode-go:qwen3.6-plus" | "opencode-go:qwen3.7-plus" | "opencode-go:qwen3.7-max" | "opencode-go:glm-5-1" | "opencode-go:kimi-k2-5" | "opencode-go:kimi-k2-6" | "opencode-go:minimax-m2-5" | "opencode-go:minimax-m2-7" | "opencode-go:mimo-v2-5" | "opencode-go:mimo-v2-5-pro" | "opencode:glm-5" | "opencode:minimax-m2.5" | "opencode:minimax-m2.7" | "opencode:glm-5-1" | "opencode:kimi-k2-5" | "opencode:kimi-k2-6" | "opencode:opus-4-6" | "openrouter:glm-5" | "openrouter:glm-5-1" | "openrouter:kimi-k2-5" | "openrouter:kimi-k2-6" | "openrouter:minimax-m2-5" | "openrouter:minimax-m2-7" | "openrouter:gpt-oss-120b" | "openrouter:gpt-5-3-codex" | "openrouter:sonnet-4.5" | "openrouter:opus-4.6" | "openrouter:owl-alpha" | "openrouter:haiku-4.5" | "xai:grok-4-1-fast")[];
|
|
3
|
+
export declare const models: readonly ("anthropic:haiku" | "anthropic:opus" | "anthropic:sonnet" | "deepseek:deepseek-v4-flash" | "deepseek:deepseek-v4-pro" | "openai:gpt-5.1-codex-mini" | "openai:gpt-5.2" | "openai:gpt-5.2-codex" | "opencode-go:deepseek-v4-flash" | "opencode-go:deepseek-v4-pro" | "opencode-go:glm-5" | "opencode-go:minimax-m3" | "opencode-go:qwen3.6-plus" | "opencode-go:qwen3.7-plus" | "opencode-go:qwen3.7-max" | "opencode-go:glm-5-1" | "opencode-go:glm-5-2" | "opencode-go:kimi-k2-5" | "opencode-go:kimi-k2-6" | "opencode-go:kimi-k2-7-code" | "opencode-go:minimax-m2-5" | "opencode-go:minimax-m2-7" | "opencode-go:mimo-v2-5" | "opencode-go:mimo-v2-5-pro" | "opencode:glm-5" | "opencode:minimax-m2.5" | "opencode:minimax-m2.7" | "opencode:glm-5-1" | "opencode:kimi-k2-5" | "opencode:kimi-k2-6" | "opencode:opus-4-6" | "openrouter:glm-5" | "openrouter:glm-5-1" | "openrouter:kimi-k2-5" | "openrouter:kimi-k2-6" | "openrouter:minimax-m2-5" | "openrouter:minimax-m2-7" | "openrouter:gpt-oss-120b" | "openrouter:gpt-5-3-codex" | "openrouter:sonnet-4.5" | "openrouter:opus-4.6" | "openrouter:owl-alpha" | "openrouter:haiku-4.5" | "xai:grok-4-1-fast")[];
|
|
4
4
|
export type ModelName = (typeof models)[number] | (`xai:${string}` & {}) | (`openai:${string}` & {}) | (`anthropic:${string}` & {}) | (`google:${string}` & {}) | (`groq:${string}` & {}) | (`deepseek:${string}` & {}) | (`openrouter:${string}` & {}) | (`opencode-go:${string}` & {}) | (`opencode:${string}` & {});
|
|
5
5
|
export declare function isSupportedModel(model: unknown): model is ModelName;
|
|
6
6
|
export declare function languageModel(model: ModelName): import("@ai-sdk/provider").LanguageModelV3;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"providers.d.ts","sourceRoot":"","sources":["../../source/models/providers.ts"],"names":[],"mappings":"AAgDA,QAAA,MAAM,SAAS,gHAUL,CAAC;AAEX,KAAK,aAAa,GAAG,CAAC,OAAO,SAAS,CAAC,CAAC,MAAM,CAAC,CAAC;AAchD,eAAO,MAAM,MAAM,
|
|
1
|
+
{"version":3,"file":"providers.d.ts","sourceRoot":"","sources":["../../source/models/providers.ts"],"names":[],"mappings":"AAgDA,QAAA,MAAM,SAAS,gHAUL,CAAC;AAEX,KAAK,aAAa,GAAG,CAAC,OAAO,SAAS,CAAC,CAAC,MAAM,CAAC,CAAC;AAchD,eAAO,MAAM,MAAM,qnCAUT,CAAC;AAEX,MAAM,MAAM,SAAS,GACjB,CAAC,OAAO,MAAM,CAAC,CAAC,MAAM,CAAC,GACvB,CAAC,OAAO,MAAM,EAAE,GAAG,EAAE,CAAC,GACtB,CAAC,UAAU,MAAM,EAAE,GAAG,EAAE,CAAC,GACzB,CAAC,aAAa,MAAM,EAAE,GAAG,EAAE,CAAC,GAC5B,CAAC,UAAU,MAAM,EAAE,GAAG,EAAE,CAAC,GACzB,CAAC,QAAQ,MAAM,EAAE,GAAG,EAAE,CAAC,GACvB,CAAC,YAAY,MAAM,EAAE,GAAG,EAAE,CAAC,GAC3B,CAAC,cAAc,MAAM,EAAE,GAAG,EAAE,CAAC,GAC7B,CAAC,eAAe,MAAM,EAAE,GAAG,EAAE,CAAC,GAC9B,CAAC,YAAY,MAAM,EAAE,GAAG,EAAE,CAAC,CAAC;AAEhC,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,OAAO,GAAG,KAAK,IAAI,SAAS,CAcnE;AAED,wBAAgB,aAAa,CAAC,KAAK,EAAE,SAAS,8CAE7C;AAED,MAAM,WAAW,aAAa,CAAC,CAAC,GAAG,SAAS;IAC1C,EAAE,EAAE,CAAC,CAAC;IACN,QAAQ,EAAE,aAAa,CAAC;IACxB,aAAa,EAAE,MAAM,CAAC;IACtB,mBAAmB,EAAE,OAAO,CAAC;IAC7B,iBAAiB,EAAE,OAAO,CAAC;IAC3B,iBAAiB,EAAE,MAAM,CAAC;IAC1B,kBAAkB,EAAE,MAAM,CAAC;IAC3B,eAAe,EAAE,MAAM,CAAC;IACxB,kBAAkB,EAAE,MAAM,CAAC;IAC3B,YAAY,EAAE,KAAK,GAAG,UAAU,GAAG,SAAS,CAAC;CAC9C;AAGD,eAAO,MAAM,aAAa,EAAE,MAAM,CAAC,SAAS,EAAE,aAAa,CAU1D,CAAC;AAGF,wBAAgB,YAAY,CAAC,SAAS,EAAE,MAAM,GAAG,SAAS,IAAI,SAAS,CAEtE"}
|
|
@@ -36,6 +36,26 @@ type SessionTokenUsage = {
|
|
|
36
36
|
estimatedCost: number;
|
|
37
37
|
};
|
|
38
38
|
};
|
|
39
|
+
/**
|
|
40
|
+
* Per-turn timing accumulated across a session.
|
|
41
|
+
* Mirrors the SessionTokenUsage shape (total plus lastTurn) so summaries can
|
|
42
|
+
* separate model response time from tool execution time.
|
|
43
|
+
*/
|
|
44
|
+
type SessionTiming = {
|
|
45
|
+
/** Aggregated totals for the entire session */
|
|
46
|
+
total: {
|
|
47
|
+
wallClockMs: number;
|
|
48
|
+
modelMs: number;
|
|
49
|
+
toolMs: number;
|
|
50
|
+
turns: number;
|
|
51
|
+
};
|
|
52
|
+
/** Last turn's timing */
|
|
53
|
+
lastTurn: {
|
|
54
|
+
wallClockMs: number;
|
|
55
|
+
modelMs: number;
|
|
56
|
+
toolMs: number;
|
|
57
|
+
};
|
|
58
|
+
};
|
|
39
59
|
/**
|
|
40
60
|
* @deprecated Use SessionTokenUsage instead. Kept for backward compatibility
|
|
41
61
|
* with older session files that may have the old format.
|
|
@@ -68,6 +88,7 @@ type SavedMessageHistory = {
|
|
|
68
88
|
updatedAt: Date;
|
|
69
89
|
messages: ModelMessage[];
|
|
70
90
|
tokenUsage?: SessionTokenUsage | TokenUsageTurn[];
|
|
91
|
+
timing?: SessionTiming;
|
|
71
92
|
metadata?: Record<string, unknown>;
|
|
72
93
|
};
|
|
73
94
|
interface MessageHistoryEvents {
|
|
@@ -86,6 +107,7 @@ export declare class SessionManager extends EventEmitter<MessageHistoryEvents> {
|
|
|
86
107
|
private modelManager;
|
|
87
108
|
private tokenTracker;
|
|
88
109
|
private tokenUsage;
|
|
110
|
+
private timing;
|
|
89
111
|
private transientMessages;
|
|
90
112
|
private metadata;
|
|
91
113
|
constructor({ stateDir, modelManager, tokenTracker, }: {
|
|
@@ -94,6 +116,7 @@ export declare class SessionManager extends EventEmitter<MessageHistoryEvents> {
|
|
|
94
116
|
tokenTracker: TokenTracker;
|
|
95
117
|
});
|
|
96
118
|
private createEmptyTokenUsage;
|
|
119
|
+
private createEmptyTiming;
|
|
97
120
|
create(modelId: string): void;
|
|
98
121
|
setModelId(modelId: string): void;
|
|
99
122
|
private validMessage;
|
|
@@ -161,6 +184,24 @@ export declare class SessionManager extends EventEmitter<MessageHistoryEvents> {
|
|
|
161
184
|
};
|
|
162
185
|
getLastTurnContextWindow(): number;
|
|
163
186
|
clearTokenUsage(): void;
|
|
187
|
+
recordTurnTiming(timing: {
|
|
188
|
+
wallClockMs: number;
|
|
189
|
+
modelMs: number;
|
|
190
|
+
toolMs: number;
|
|
191
|
+
}): void;
|
|
192
|
+
/**
|
|
193
|
+
* Session-level rollup of timing data. Returns null when no timing has been
|
|
194
|
+
* recorded for the session. `overheadMs` is non-tool, non-model wall-clock
|
|
195
|
+
* time, and `toolTimeRatio` is the fraction of total wall-clock spent in tools.
|
|
196
|
+
*/
|
|
197
|
+
getTimingSummary(): {
|
|
198
|
+
wallClockMs: number;
|
|
199
|
+
modelMs: number;
|
|
200
|
+
toolMs: number;
|
|
201
|
+
overheadMs: number;
|
|
202
|
+
toolTimeRatio: number;
|
|
203
|
+
turns: number;
|
|
204
|
+
} | null;
|
|
164
205
|
}
|
|
165
206
|
export {};
|
|
166
207
|
//# sourceMappingURL=manager.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"manager.d.ts","sourceRoot":"","sources":["../../source/sessions/manager.ts"],"names":[],"mappings":"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
|
|
1
|
+
{"version":3,"file":"manager.d.ts","sourceRoot":"","sources":["../../source/sessions/manager.ts"],"names":[],"mappings":"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"}
|
package/dist/sessions/manager.js
CHANGED
|
@@ -113,6 +113,7 @@ export class SessionManager extends EventEmitter {
|
|
|
113
113
|
modelManager;
|
|
114
114
|
tokenTracker;
|
|
115
115
|
tokenUsage;
|
|
116
|
+
timing;
|
|
116
117
|
transientMessages = [];
|
|
117
118
|
metadata = {};
|
|
118
119
|
constructor({ stateDir, modelManager, tokenTracker, }) {
|
|
@@ -128,6 +129,7 @@ export class SessionManager extends EventEmitter {
|
|
|
128
129
|
this.modelManager = modelManager;
|
|
129
130
|
this.tokenTracker = tokenTracker;
|
|
130
131
|
this.tokenUsage = null;
|
|
132
|
+
this.timing = null;
|
|
131
133
|
}
|
|
132
134
|
createEmptyTokenUsage() {
|
|
133
135
|
return {
|
|
@@ -149,6 +151,21 @@ export class SessionManager extends EventEmitter {
|
|
|
149
151
|
},
|
|
150
152
|
};
|
|
151
153
|
}
|
|
154
|
+
createEmptyTiming() {
|
|
155
|
+
return {
|
|
156
|
+
total: {
|
|
157
|
+
wallClockMs: 0,
|
|
158
|
+
modelMs: 0,
|
|
159
|
+
toolMs: 0,
|
|
160
|
+
turns: 0,
|
|
161
|
+
},
|
|
162
|
+
lastTurn: {
|
|
163
|
+
wallClockMs: 0,
|
|
164
|
+
modelMs: 0,
|
|
165
|
+
toolMs: 0,
|
|
166
|
+
},
|
|
167
|
+
};
|
|
168
|
+
}
|
|
152
169
|
create(modelId) {
|
|
153
170
|
this.clear();
|
|
154
171
|
this.modelId = modelId;
|
|
@@ -205,6 +222,7 @@ export class SessionManager extends EventEmitter {
|
|
|
205
222
|
this.metadata = {};
|
|
206
223
|
this.contextWindow = 0;
|
|
207
224
|
this.tokenUsage = null;
|
|
225
|
+
this.timing = null;
|
|
208
226
|
this.emit("clear-history");
|
|
209
227
|
}
|
|
210
228
|
setContextWindow(contextWindow) {
|
|
@@ -290,6 +308,7 @@ export class SessionManager extends EventEmitter {
|
|
|
290
308
|
updatedAt: this.updatedAt,
|
|
291
309
|
messages: this.history,
|
|
292
310
|
...(this.tokenUsage !== null ? { tokenUsage: this.tokenUsage } : {}),
|
|
311
|
+
...(this.timing !== null ? { timing: this.timing } : {}),
|
|
293
312
|
metadata: Object.keys(this.metadata).length > 0 ? this.metadata : undefined,
|
|
294
313
|
};
|
|
295
314
|
try {
|
|
@@ -583,6 +602,7 @@ React Component Rendering Debug";
|
|
|
583
602
|
else {
|
|
584
603
|
this.tokenUsage = null;
|
|
585
604
|
}
|
|
605
|
+
this.timing = savedHistory.timing ?? null;
|
|
586
606
|
this.metadata = savedHistory.metadata ?? {};
|
|
587
607
|
}
|
|
588
608
|
// Token usage tracking methods
|
|
@@ -640,4 +660,37 @@ React Component Rendering Debug";
|
|
|
640
660
|
clearTokenUsage() {
|
|
641
661
|
this.tokenUsage = null;
|
|
642
662
|
}
|
|
663
|
+
recordTurnTiming(timing) {
|
|
664
|
+
if (this.timing === null) {
|
|
665
|
+
this.timing = this.createEmptyTiming();
|
|
666
|
+
}
|
|
667
|
+
this.timing.total.wallClockMs += timing.wallClockMs;
|
|
668
|
+
this.timing.total.modelMs += timing.modelMs;
|
|
669
|
+
this.timing.total.toolMs += timing.toolMs;
|
|
670
|
+
this.timing.total.turns += 1;
|
|
671
|
+
this.timing.lastTurn.wallClockMs = timing.wallClockMs;
|
|
672
|
+
this.timing.lastTurn.modelMs = timing.modelMs;
|
|
673
|
+
this.timing.lastTurn.toolMs = timing.toolMs;
|
|
674
|
+
}
|
|
675
|
+
/**
|
|
676
|
+
* Session-level rollup of timing data. Returns null when no timing has been
|
|
677
|
+
* recorded for the session. `overheadMs` is non-tool, non-model wall-clock
|
|
678
|
+
* time, and `toolTimeRatio` is the fraction of total wall-clock spent in tools.
|
|
679
|
+
*/
|
|
680
|
+
getTimingSummary() {
|
|
681
|
+
if (this.timing === null) {
|
|
682
|
+
return null;
|
|
683
|
+
}
|
|
684
|
+
const { wallClockMs, modelMs, toolMs, turns } = this.timing.total;
|
|
685
|
+
const overheadMs = Math.max(0, wallClockMs - modelMs - toolMs);
|
|
686
|
+
const toolTimeRatio = wallClockMs > 0 ? toolMs / wallClockMs : 0;
|
|
687
|
+
return {
|
|
688
|
+
wallClockMs,
|
|
689
|
+
modelMs,
|
|
690
|
+
toolMs,
|
|
691
|
+
overheadMs,
|
|
692
|
+
toolTimeRatio,
|
|
693
|
+
turns,
|
|
694
|
+
};
|
|
695
|
+
}
|
|
643
696
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"summary.d.ts","sourceRoot":"","sources":["../../source/sessions/summary.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,cAAc,CAAC;
|
|
1
|
+
{"version":3,"file":"summary.d.ts","sourceRoot":"","sources":["../../source/sessions/summary.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,cAAc,CAAC;AAgDnD,wBAAgB,gBAAgB,CAC9B,cAAc,EAAE,cAAc,EAC9B,SAAS,CAAC,EAAE,OAAO,GAClB,IAAI,CAIN;AAED,wBAAgB,gBAAgB,CAC9B,cAAc,EAAE,cAAc,EAC9B,SAAS,CAAC,EAAE,OAAO,GAClB,IAAI,CAGN"}
|
package/dist/sessions/summary.js
CHANGED
|
@@ -16,9 +16,27 @@ function formatSummary(sessionManager, noSession) {
|
|
|
16
16
|
lines.push(` File: ${filePath}`);
|
|
17
17
|
lines.push(` Resume: acai --resume ${sessionId}`);
|
|
18
18
|
}
|
|
19
|
+
const timing = sessionManager.getTimingSummary();
|
|
20
|
+
if (timing && timing.wallClockMs > 0) {
|
|
21
|
+
const toolPct = (timing.toolTimeRatio * 100).toFixed(1);
|
|
22
|
+
lines.push("");
|
|
23
|
+
lines.push(" Timing");
|
|
24
|
+
lines.push(` Turns: ${timing.turns}`);
|
|
25
|
+
lines.push(` Total: ${formatMs(timing.wallClockMs)}`);
|
|
26
|
+
lines.push(` Model: ${formatMs(timing.modelMs)}`);
|
|
27
|
+
lines.push(` Tools: ${formatMs(timing.toolMs)}`);
|
|
28
|
+
lines.push(` Overhead: ${formatMs(timing.overheadMs)}`);
|
|
29
|
+
lines.push(` Tool/Total: ${toolPct}%`);
|
|
30
|
+
}
|
|
19
31
|
lines.push("");
|
|
20
32
|
return lines.join("\n");
|
|
21
33
|
}
|
|
34
|
+
function formatMs(ms) {
|
|
35
|
+
if (ms < 1000) {
|
|
36
|
+
return `${Math.round(ms)}ms`;
|
|
37
|
+
}
|
|
38
|
+
return `${(ms / 1000).toFixed(2)}s`;
|
|
39
|
+
}
|
|
22
40
|
export function writeExitSummary(sessionManager, noSession) {
|
|
23
41
|
const summary = formatSummary(sessionManager, noSession);
|
|
24
42
|
const summaryPath = join(tmpdir(), "acai-exit-summary.txt");
|
package/dist/utils/bash.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"bash.d.ts","sourceRoot":"","sources":["../../source/utils/bash.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"bash.d.ts","sourceRoot":"","sources":["../../source/utils/bash.ts"],"names":[],"mappings":"AAgQA,wBAAgB,aAAa,CAC3B,OAAO,EAAE,MAAM,EACf,WAAW,EAAE,MAAM,EAAE,EACrB,GAAG,EAAE,MAAM,GACV;IAAE,OAAO,EAAE,OAAO,CAAC;IAAC,KAAK,CAAC,EAAE,MAAM,CAAA;CAAE,CAiBtC;AAiFD,eAAO,MAAM,UAAU,GACrB,UAAU,MAAM,GAAG,IAAI,GAAG,SAAS,EACnC,YAAY,MAAM,EAClB,cAAc,MAAM,EAAE,KACrB,MAaF,CAAC;AA8EF,eAAO,MAAM,iBAAiB,GAAI,YAAY,MAAM,KAAG,OAetD,CAAC"}
|
package/dist/utils/bash.js
CHANGED
|
@@ -131,36 +131,71 @@ function computeSkipTokenMask(tokens) {
|
|
|
131
131
|
}
|
|
132
132
|
return skip;
|
|
133
133
|
}
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
134
|
+
// Extract a path embedded in a flag-style argument (--key=value or -X/path).
|
|
135
|
+
// Returns null if no path-like segment is found.
|
|
136
|
+
function extractPathFromFlag(flag) {
|
|
137
|
+
// --key=value style: extract and de-quote the value side
|
|
138
|
+
const eqIndex = flag.indexOf("=");
|
|
139
|
+
if (eqIndex !== -1) {
|
|
140
|
+
const value = stripQuotes(flag.slice(eqIndex + 1));
|
|
141
|
+
// Only treat as a path if it starts with a path-identifying prefix
|
|
142
|
+
// to avoid validating string arguments that happen to contain "/"
|
|
143
|
+
if (value.startsWith("/") ||
|
|
144
|
+
value.startsWith("~") ||
|
|
145
|
+
value.startsWith("./") ||
|
|
146
|
+
value.startsWith("../")) {
|
|
147
|
+
return value;
|
|
148
|
+
}
|
|
149
|
+
return null;
|
|
139
150
|
}
|
|
140
|
-
//
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
if (cleanToken.startsWith("-") ||
|
|
144
|
-
cleanToken.includes("://") ||
|
|
145
|
-
(!cleanToken.includes("/") && cleanToken !== "~")) {
|
|
146
|
-
return { isValid: true };
|
|
151
|
+
// -X/path style: single letter flag directly attached to a path
|
|
152
|
+
if (/^-[a-zA-Z]\//.test(flag)) {
|
|
153
|
+
return flag.slice(2);
|
|
147
154
|
}
|
|
155
|
+
return null;
|
|
156
|
+
}
|
|
157
|
+
function validatePathValue(rawPath, allowedDirs, cwd) {
|
|
148
158
|
try {
|
|
149
159
|
// Expand ~ to home directory for proper validation
|
|
150
|
-
const
|
|
151
|
-
? path.join(os.homedir(),
|
|
152
|
-
:
|
|
153
|
-
const resolvedPath = path.resolve(cwd,
|
|
160
|
+
const expandedPath = rawPath.startsWith("~/") || rawPath === "~"
|
|
161
|
+
? path.join(os.homedir(), rawPath.slice(1))
|
|
162
|
+
: rawPath;
|
|
163
|
+
const resolvedPath = path.resolve(cwd, expandedPath);
|
|
154
164
|
if (!isPathWithinAllowedDirs(resolvedPath, allowedDirs)) {
|
|
155
165
|
return {
|
|
156
166
|
isValid: false,
|
|
157
|
-
error: `Path '${
|
|
167
|
+
error: `Path '${rawPath}' resolves outside the allowed directories (${resolvedPath}). All paths must be within ${allowedDirs.join(", ")}`,
|
|
158
168
|
};
|
|
159
169
|
}
|
|
160
170
|
}
|
|
161
171
|
catch (_e) { }
|
|
162
172
|
return { isValid: true };
|
|
163
173
|
}
|
|
174
|
+
function validateToken(token, allowedDirs, cwd) {
|
|
175
|
+
// Skip fully quoted tokens - they're string arguments, not paths
|
|
176
|
+
// (paths are typically unquoted or only quoted to handle spaces)
|
|
177
|
+
if (isFullyQuoted(token)) {
|
|
178
|
+
return { isValid: true };
|
|
179
|
+
}
|
|
180
|
+
// Remove quotes for path checking
|
|
181
|
+
const cleanToken = stripQuotes(token);
|
|
182
|
+
// Skip URLs and non-path tokens
|
|
183
|
+
if (cleanToken.includes("://") ||
|
|
184
|
+
(!cleanToken.includes("/") && cleanToken !== "~")) {
|
|
185
|
+
return { isValid: true };
|
|
186
|
+
}
|
|
187
|
+
// For flag-style tokens that embed paths (e.g., --file=/etc/passwd, -o/tmp/x),
|
|
188
|
+
// extract the embedded path for validation.
|
|
189
|
+
if (cleanToken.startsWith("-")) {
|
|
190
|
+
const embeddedPath = extractPathFromFlag(cleanToken);
|
|
191
|
+
if (!embeddedPath) {
|
|
192
|
+
// No embedded path found — just a flag
|
|
193
|
+
return { isValid: true };
|
|
194
|
+
}
|
|
195
|
+
return validatePathValue(embeddedPath, allowedDirs, cwd);
|
|
196
|
+
}
|
|
197
|
+
return validatePathValue(cleanToken, allowedDirs, cwd);
|
|
198
|
+
}
|
|
164
199
|
// Validate path arguments to ensure they're within the project
|
|
165
200
|
export function validatePaths(command, allowedDirs, cwd) {
|
|
166
201
|
const tokens = tokenizeShellWords(command);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"command-protection.d.ts","sourceRoot":"","sources":["../../source/utils/command-protection.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"command-protection.d.ts","sourceRoot":"","sources":["../../source/utils/command-protection.ts"],"names":[],"mappings":"AAMA,MAAM,WAAW,oBAAoB;IACnC,OAAO,EAAE,IAAI,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,MAAM,CAAC;IAChB,GAAG,EAAE,MAAM,CAAC;CACb;AAED,UAAU,iBAAiB;IACzB,OAAO,EAAE,KAAK,CAAC;CAChB;AAoBD;;GAEG;AACH,KAAK,mBAAmB,GAAG,oBAAoB,GAAG,iBAAiB,CAAC;AA0EpE;;;;GAIG;AACH,wBAAgB,wBAAwB,CAAC,OAAO,EAAE,MAAM,GAAG,mBAAmB,CA4B7E;AA0fD;;GAEG;AACH,wBAAgB,2BAA2B,CACzC,MAAM,EAAE,oBAAoB,GAC3B,MAAM,CAQR"}
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
* Command Protection Module
|
|
3
3
|
* Detects and blocks destructive commands that could cause data loss
|
|
4
4
|
*/
|
|
5
|
+
import path from "node:path";
|
|
5
6
|
/**
|
|
6
7
|
* Check if a git subcommand appears as an actual command (not inside quoted strings).
|
|
7
8
|
* This helps avoid false positives when destructive command text appears in
|
|
@@ -198,49 +199,168 @@ function detectDestructiveGitCommands(command) {
|
|
|
198
199
|
* Detect dangerous rm -rf commands outside of temporary directories
|
|
199
200
|
*/
|
|
200
201
|
function detectDangerousRmRf(command) {
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
202
|
+
const tokens = tokenizeShellCommand(command);
|
|
203
|
+
for (let i = 0; i < tokens.length; i++) {
|
|
204
|
+
const token = tokens[i];
|
|
205
|
+
if (!token || token.separator || token.value !== "rm")
|
|
206
|
+
continue;
|
|
207
|
+
const rmArgs = collectCommandArguments(tokens, i + 1);
|
|
208
|
+
if (!isRecursiveForceRm(rmArgs))
|
|
209
|
+
continue;
|
|
210
|
+
const paths = getRmPathArguments(rmArgs);
|
|
211
|
+
if (paths.length === 0)
|
|
212
|
+
continue;
|
|
213
|
+
if (paths.every(isTempDirectoryOnly))
|
|
214
|
+
continue;
|
|
215
|
+
return {
|
|
216
|
+
blocked: true,
|
|
217
|
+
reason: "rm -rf outside of temporary directories can cause permanent data loss",
|
|
218
|
+
command,
|
|
219
|
+
tip: "Only rm -rf is allowed for /tmp/*, /var/tmp/*, or $TMPDIR/* to clean temporary files.",
|
|
220
|
+
};
|
|
205
221
|
}
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
222
|
+
return { blocked: false };
|
|
223
|
+
}
|
|
224
|
+
function tokenizeShellCommand(command) {
|
|
225
|
+
const state = {
|
|
226
|
+
tokens: [],
|
|
227
|
+
current: "",
|
|
228
|
+
inSingleQuote: false,
|
|
229
|
+
inDoubleQuote: false,
|
|
230
|
+
escaped: false,
|
|
231
|
+
};
|
|
232
|
+
for (let i = 0; i < command.length; i++) {
|
|
233
|
+
consumeShellChar(state, command[i] ?? "");
|
|
234
|
+
}
|
|
235
|
+
pushShellToken(state);
|
|
236
|
+
return state.tokens;
|
|
237
|
+
}
|
|
238
|
+
function consumeShellChar(state, char) {
|
|
239
|
+
if (state.escaped) {
|
|
240
|
+
state.current += char;
|
|
241
|
+
state.escaped = false;
|
|
242
|
+
return;
|
|
243
|
+
}
|
|
244
|
+
if (char === "\\" && !state.inSingleQuote) {
|
|
245
|
+
state.escaped = true;
|
|
246
|
+
return;
|
|
212
247
|
}
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
248
|
+
if (char === "'" && !state.inDoubleQuote) {
|
|
249
|
+
state.inSingleQuote = !state.inSingleQuote;
|
|
250
|
+
return;
|
|
251
|
+
}
|
|
252
|
+
if (char === '"' && !state.inSingleQuote) {
|
|
253
|
+
state.inDoubleQuote = !state.inDoubleQuote;
|
|
254
|
+
return;
|
|
255
|
+
}
|
|
256
|
+
if (isTokenBoundary(state, char)) {
|
|
257
|
+
pushShellToken(state);
|
|
258
|
+
if (char === "\n" || isShellSeparator(char)) {
|
|
259
|
+
state.tokens.push({ value: char, separator: true });
|
|
221
260
|
}
|
|
222
|
-
return
|
|
223
|
-
}
|
|
224
|
-
|
|
225
|
-
|
|
261
|
+
return;
|
|
262
|
+
}
|
|
263
|
+
state.current += char;
|
|
264
|
+
}
|
|
265
|
+
function isTokenBoundary(state, char) {
|
|
266
|
+
return (!state.inSingleQuote &&
|
|
267
|
+
!state.inDoubleQuote &&
|
|
268
|
+
(/\s/.test(char) || isShellSeparator(char)));
|
|
269
|
+
}
|
|
270
|
+
function pushShellToken(state) {
|
|
271
|
+
if (!state.current)
|
|
272
|
+
return;
|
|
273
|
+
state.tokens.push({ value: state.current, separator: false });
|
|
274
|
+
state.current = "";
|
|
275
|
+
}
|
|
276
|
+
function isShellSeparator(char) {
|
|
277
|
+
return char === ";" || char === "|" || char === "&";
|
|
278
|
+
}
|
|
279
|
+
function collectCommandArguments(tokens, startIndex) {
|
|
280
|
+
const args = [];
|
|
281
|
+
for (let i = startIndex; i < tokens.length; i++) {
|
|
282
|
+
const token = tokens[i];
|
|
283
|
+
if (!token || token.separator)
|
|
284
|
+
break;
|
|
285
|
+
args.push(token.value);
|
|
286
|
+
}
|
|
287
|
+
return args;
|
|
288
|
+
}
|
|
289
|
+
function isRecursiveForceRm(args) {
|
|
290
|
+
const flags = { recursive: false, force: false };
|
|
291
|
+
for (const arg of args) {
|
|
292
|
+
if (arg === "--")
|
|
293
|
+
break;
|
|
294
|
+
updateRmFlagState(flags, arg);
|
|
295
|
+
}
|
|
296
|
+
return flags.recursive && flags.force;
|
|
297
|
+
}
|
|
298
|
+
function updateRmFlagState(flags, arg) {
|
|
299
|
+
if (arg === "--recursive") {
|
|
300
|
+
flags.recursive = true;
|
|
301
|
+
return;
|
|
302
|
+
}
|
|
303
|
+
if (arg === "--force") {
|
|
304
|
+
flags.force = true;
|
|
305
|
+
return;
|
|
306
|
+
}
|
|
307
|
+
if (!/^-[^-]+$/.test(arg))
|
|
308
|
+
return;
|
|
309
|
+
if (arg.includes("r") || arg.includes("R"))
|
|
310
|
+
flags.recursive = true;
|
|
311
|
+
if (arg.includes("f"))
|
|
312
|
+
flags.force = true;
|
|
313
|
+
}
|
|
314
|
+
function getRmPathArguments(args) {
|
|
315
|
+
const paths = [];
|
|
316
|
+
let parsingOptions = true;
|
|
317
|
+
for (const arg of args) {
|
|
318
|
+
if (parsingOptions && arg === "--") {
|
|
319
|
+
parsingOptions = false;
|
|
320
|
+
continue;
|
|
321
|
+
}
|
|
322
|
+
if (parsingOptions && isRmOption(arg))
|
|
323
|
+
continue;
|
|
324
|
+
paths.push(arg);
|
|
325
|
+
}
|
|
326
|
+
return paths;
|
|
327
|
+
}
|
|
328
|
+
function isRmOption(arg) {
|
|
329
|
+
return arg.startsWith("-") && arg !== "-";
|
|
330
|
+
}
|
|
331
|
+
function isTempDirectoryOnly(inputPath) {
|
|
226
332
|
const tmpDirVar = "$" + "TMPDIR";
|
|
227
333
|
const tmpDirVarBraces = "$" + "{TMPDIR}";
|
|
228
|
-
if (
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
afterRmRf.startsWith(`${tmpDirVarBraces}/`)) {
|
|
232
|
-
return { blocked: false };
|
|
334
|
+
if (isTempVariablePath(inputPath, tmpDirVar) ||
|
|
335
|
+
isTempVariablePath(inputPath, tmpDirVarBraces)) {
|
|
336
|
+
return true;
|
|
233
337
|
}
|
|
234
|
-
if (
|
|
235
|
-
return
|
|
338
|
+
if (!inputPath.startsWith("/"))
|
|
339
|
+
return false;
|
|
340
|
+
const normalizedPath = normalizeAbsolutePath(inputPath);
|
|
341
|
+
const tempDirs = ["/tmp", "/var/tmp", process.env["TMPDIR"] || "/tmp"].map(normalizeAbsolutePath);
|
|
342
|
+
return tempDirs.some((tempDir) => normalizedPath === tempDir || normalizedPath.startsWith(`${tempDir}/`));
|
|
343
|
+
}
|
|
344
|
+
function isTempVariablePath(inputPath, variable) {
|
|
345
|
+
if (inputPath !== variable && !inputPath.startsWith(`${variable}/`)) {
|
|
346
|
+
return false;
|
|
236
347
|
}
|
|
237
|
-
|
|
238
|
-
return
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
348
|
+
const suffix = inputPath.slice(variable.length);
|
|
349
|
+
return !suffix.split("/").includes("..");
|
|
350
|
+
}
|
|
351
|
+
function normalizeAbsolutePath(inputPath) {
|
|
352
|
+
return path.posix.normalize(inputPath.replace(/\/+$/, "") || "/");
|
|
353
|
+
}
|
|
354
|
+
function detectDangerousRmRfInText(content) {
|
|
355
|
+
const rmCommandPattern = /\brm\b[^\n;&|)]*/gi;
|
|
356
|
+
let match = rmCommandPattern.exec(content);
|
|
357
|
+
while (match !== null) {
|
|
358
|
+
const rmResult = detectDangerousRmRf(match[0]);
|
|
359
|
+
if (rmResult.blocked)
|
|
360
|
+
return rmResult;
|
|
361
|
+
match = rmCommandPattern.exec(content);
|
|
362
|
+
}
|
|
363
|
+
return { blocked: false };
|
|
244
364
|
}
|
|
245
365
|
/**
|
|
246
366
|
* Detect dangerous patterns in inline scripts (-c flags)
|
|
@@ -268,9 +388,6 @@ function detectDangerousInlineScripts(command) {
|
|
|
268
388
|
/git\s+push\s+(-f|--force)/i,
|
|
269
389
|
/git\s+branch\s+-[A-Z]/i,
|
|
270
390
|
/git\s+stash\s+(drop|clear)/i,
|
|
271
|
-
/rm\s+-rf\s+\/home/i,
|
|
272
|
-
/rm\s+-rf\s+\/usr/i,
|
|
273
|
-
/rm\s+-rf\s+~/i,
|
|
274
391
|
];
|
|
275
392
|
// Check if any destructive pattern is present in the command
|
|
276
393
|
for (const destructivePattern of destructivePatterns) {
|
|
@@ -283,6 +400,15 @@ function detectDangerousInlineScripts(command) {
|
|
|
283
400
|
};
|
|
284
401
|
}
|
|
285
402
|
}
|
|
403
|
+
const rmResult = detectDangerousRmRfInText(command);
|
|
404
|
+
if (rmResult.blocked) {
|
|
405
|
+
return {
|
|
406
|
+
blocked: true,
|
|
407
|
+
reason: `Inline ${language} script contains destructive operation`,
|
|
408
|
+
command,
|
|
409
|
+
tip: "Review the script content for destructive commands.",
|
|
410
|
+
};
|
|
411
|
+
}
|
|
286
412
|
}
|
|
287
413
|
}
|
|
288
414
|
return { blocked: false };
|
|
@@ -371,34 +497,12 @@ function detectDangerousScriptContent(content) {
|
|
|
371
497
|
};
|
|
372
498
|
}
|
|
373
499
|
}
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
/\brm\s+-rf\s+\/var\s*$/i, // Block /var alone but allow /var/tmp
|
|
381
|
-
/\brm\s+-rf\s+~(?!\/)/i, // rm -rf ~ but allow ~/tmp
|
|
382
|
-
];
|
|
383
|
-
for (const pattern of dangerousRmPatterns) {
|
|
384
|
-
if (pattern.test(content)) {
|
|
385
|
-
return {
|
|
386
|
-
blocked: true,
|
|
387
|
-
tip: "The script contains a dangerous rm -rf command.",
|
|
388
|
-
};
|
|
389
|
-
}
|
|
390
|
-
}
|
|
391
|
-
// Check for format string attacks and other dangerous patterns
|
|
392
|
-
const dangerousPatterns = [
|
|
393
|
-
/\brm\s+-rf\s+\$\w+/i, // rm -rf $VAR (variable expansion could be dangerous)
|
|
394
|
-
];
|
|
395
|
-
for (const pattern of dangerousPatterns) {
|
|
396
|
-
if (pattern.test(content)) {
|
|
397
|
-
return {
|
|
398
|
-
blocked: true,
|
|
399
|
-
tip: "The script contains a potentially dangerous rm command with variable expansion.",
|
|
400
|
-
};
|
|
401
|
-
}
|
|
500
|
+
const rmResult = detectDangerousRmRfInText(content);
|
|
501
|
+
if (rmResult.blocked) {
|
|
502
|
+
return {
|
|
503
|
+
blocked: true,
|
|
504
|
+
tip: "The script contains a dangerous rm -rf command.",
|
|
505
|
+
};
|
|
402
506
|
}
|
|
403
507
|
return { blocked: false };
|
|
404
508
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@travisennis/acai",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.14",
|
|
4
4
|
"description": "An AI assistant for developing software.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -60,22 +60,22 @@
|
|
|
60
60
|
"lint:length": "find source -name '*.ts' | xargs -I{} awk 'END{if(NR>500)print FILENAME\": \"NR\" lines\"}' {}"
|
|
61
61
|
},
|
|
62
62
|
"dependencies": {
|
|
63
|
-
"@ai-sdk/anthropic": "^3.0.
|
|
64
|
-
"@ai-sdk/deepseek": "^2.0.
|
|
65
|
-
"@ai-sdk/devtools": "^0.0.
|
|
66
|
-
"@ai-sdk/google": "^3.0.
|
|
67
|
-
"@ai-sdk/groq": "^3.0.
|
|
68
|
-
"@ai-sdk/open-responses": "^1.0.
|
|
69
|
-
"@ai-sdk/openai": "^3.0.
|
|
70
|
-
"@ai-sdk/openai-compatible": "^2.0.
|
|
63
|
+
"@ai-sdk/anthropic": "^3.0.84",
|
|
64
|
+
"@ai-sdk/deepseek": "^2.0.38",
|
|
65
|
+
"@ai-sdk/devtools": "^0.0.19",
|
|
66
|
+
"@ai-sdk/google": "^3.0.82",
|
|
67
|
+
"@ai-sdk/groq": "^3.0.41",
|
|
68
|
+
"@ai-sdk/open-responses": "^1.0.18",
|
|
69
|
+
"@ai-sdk/openai": "^3.0.71",
|
|
70
|
+
"@ai-sdk/openai-compatible": "^2.0.50",
|
|
71
71
|
"@crosscopy/clipboard": "^0.3.6",
|
|
72
72
|
"@travisennis/stdlib": "^0.0.14",
|
|
73
|
-
"ai": "^6.0.
|
|
73
|
+
"ai": "^6.0.204",
|
|
74
74
|
"cheerio": "^1.2.0",
|
|
75
75
|
"diff": "^9.0.0",
|
|
76
76
|
"fdir": "^6.5.0",
|
|
77
77
|
"highlight.js": "^11.11.1",
|
|
78
|
-
"marked": "18.0.
|
|
78
|
+
"marked": "18.0.5",
|
|
79
79
|
"p-throttle": "^8.1.0",
|
|
80
80
|
"parse5": "^8.0.1",
|
|
81
81
|
"parse5-htmlparser2-tree-adapter": "^8.0.1",
|
|
@@ -87,16 +87,16 @@
|
|
|
87
87
|
},
|
|
88
88
|
"devDependencies": {
|
|
89
89
|
"@ai-sdk/provider": "^3.0.10",
|
|
90
|
-
"@biomejs/biome": "2.
|
|
90
|
+
"@biomejs/biome": "2.5.0",
|
|
91
91
|
"@commitlint/config-conventional": "^21.0.2",
|
|
92
|
-
"@types/node": "^25.9.
|
|
92
|
+
"@types/node": "^25.9.3",
|
|
93
93
|
"c8": "^11.0.0",
|
|
94
94
|
"commitlint": "^21.0.2",
|
|
95
95
|
"domhandler": "^6.0.1",
|
|
96
|
-
"fallow": "^2.
|
|
96
|
+
"fallow": "^2.96.0",
|
|
97
97
|
"husky": "^9.1.7",
|
|
98
|
-
"knip": "^6.
|
|
99
|
-
"oxlint": "^1.
|
|
98
|
+
"knip": "^6.16.1",
|
|
99
|
+
"oxlint": "^1.69.0",
|
|
100
100
|
"oxlint-tsgolint": "^0.23.0",
|
|
101
101
|
"typescript": "^6.0.3"
|
|
102
102
|
},
|