@amodalai/runtime 0.1.14 → 0.1.15

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.
@@ -0,0 +1,295 @@
1
+ /**
2
+ * @license
3
+ * Copyright 2026 Amodal Labs, Inc.
4
+ * SPDX-License-Identifier: MIT
5
+ */
6
+ import { Router } from 'express';
7
+ import { buildEvalRun, judgeAllAssertions, computeEvalCost, aggregateRunCost } from '@amodalai/core';
8
+ /**
9
+ * Run a query against /chat and stream events back to the eval client.
10
+ * Returns the accumulated response, tool calls, and usage.
11
+ */
12
+ async function streamQuery(baseUrl, message, evalRes, evalName, appId) {
13
+ const chatRes = await fetch(`${baseUrl}/chat`, {
14
+ method: 'POST',
15
+ headers: { 'Content-Type': 'application/json' },
16
+ body: JSON.stringify({ message, app_id: appId ?? 'eval-runner' }),
17
+ });
18
+ const text = await chatRes.text();
19
+ const lines = text.split('\n');
20
+ let fullResponse = '';
21
+ const toolCalls = [];
22
+ const toolResults = [];
23
+ let usage;
24
+ for (const line of lines) {
25
+ if (!line.startsWith('data: '))
26
+ continue;
27
+ try {
28
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion -- SSE parsing
29
+ const event = JSON.parse(line.substring(6));
30
+ const eventType = String(event['type'] ?? '');
31
+ if (eventType === 'text_delta') {
32
+ const content = String(event['content'] ?? '');
33
+ fullResponse += content;
34
+ writeSSE(evalRes, { type: 'agent_text', evalName, content });
35
+ }
36
+ else if (eventType === 'tool_call_start') {
37
+ const params = (event['parameters'] ?? {}); // eslint-disable-line @typescript-eslint/no-unsafe-type-assertion
38
+ toolCalls.push({ name: String(event['tool_name'] ?? ''), parameters: params });
39
+ writeSSE(evalRes, { type: 'agent_tool', evalName, toolName: event['tool_name'], parameters: params });
40
+ }
41
+ else if (eventType === 'tool_call_result') {
42
+ // Capture result data so the judge knows tool calls returned real data
43
+ const resultPreview = String(event['result'] ?? event['error'] ?? '');
44
+ toolResults.push(`${String(event['tool_name'] ?? 'request')}: ${resultPreview.slice(0, 500)}`);
45
+ writeSSE(evalRes, { type: 'agent_tool_result', evalName, toolName: event['tool_name'] ?? 'request', status: event['status'], durationMs: event['duration_ms'] });
46
+ }
47
+ else if (eventType === 'done' && event['usage']) {
48
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
49
+ const u = event['usage'];
50
+ if (u.input_tokens > 0 || u.output_tokens > 0) {
51
+ usage = { inputTokens: u.input_tokens, outputTokens: u.output_tokens };
52
+ }
53
+ }
54
+ }
55
+ catch {
56
+ // skip
57
+ }
58
+ }
59
+ if (!usage) {
60
+ const outputChars = fullResponse.length;
61
+ const estimatedOutput = Math.ceil(outputChars / 4);
62
+ usage = { inputTokens: estimatedOutput * 3, outputTokens: estimatedOutput };
63
+ }
64
+ return { response: fullResponse, toolCalls, toolResults, usage };
65
+ }
66
+ /**
67
+ * Create a JudgeProvider that uses the local /chat endpoint and tracks token usage.
68
+ */
69
+ function createLocalJudgeProvider(baseUrl) {
70
+ const tracked = {
71
+ totalInputTokens: 0,
72
+ totalOutputTokens: 0,
73
+ judge: async (prompt) => {
74
+ const response = await fetch(`${baseUrl}/chat`, {
75
+ method: 'POST',
76
+ headers: { 'Content-Type': 'application/json' },
77
+ body: JSON.stringify({ message: prompt, app_id: 'eval-judge', session_id: `judge-${Date.now()}` }),
78
+ });
79
+ const text = await response.text();
80
+ let result = '';
81
+ for (const line of text.split('\n')) {
82
+ if (!line.startsWith('data: '))
83
+ continue;
84
+ try {
85
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion -- SSE parsing
86
+ const event = JSON.parse(line.substring(6));
87
+ if (event['type'] === 'text_delta') {
88
+ result += String(event['content'] ?? '');
89
+ }
90
+ else if (event['type'] === 'done' && event['usage']) {
91
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
92
+ const u = event['usage'];
93
+ tracked.totalInputTokens += u.input_tokens || 0;
94
+ tracked.totalOutputTokens += u.output_tokens || 0;
95
+ }
96
+ }
97
+ catch {
98
+ // skip
99
+ }
100
+ }
101
+ // Estimate if no usage reported
102
+ if (tracked.totalInputTokens === 0) {
103
+ tracked.totalInputTokens += Math.ceil(prompt.length / 4);
104
+ tracked.totalOutputTokens += Math.ceil(result.length / 4);
105
+ }
106
+ return result;
107
+ },
108
+ };
109
+ return tracked;
110
+ }
111
+ function writeSSE(res, data) {
112
+ res.write(`data: ${JSON.stringify(data)}\n\n`);
113
+ }
114
+ export function createEvalRouter(options) {
115
+ const router = Router();
116
+ /** List eval definitions from the repo */
117
+ router.get('/api/evals/suites', (_req, res) => {
118
+ const repo = options.sessionManager.getRepo();
119
+ const suites = repo.evals.map((e) => ({
120
+ name: e.name,
121
+ title: e.title,
122
+ description: e.description,
123
+ query: e.query,
124
+ assertionCount: e.assertions.length,
125
+ assertions: e.assertions.map((a) => ({ text: a.text, negated: a.negated })),
126
+ location: e.location,
127
+ }));
128
+ res.json({ suites });
129
+ });
130
+ /** List saved eval runs */
131
+ router.get('/api/evals/runs', (_req, res) => {
132
+ const runs = options.evalStore.list();
133
+ res.json({ runs });
134
+ });
135
+ /** Get a single eval run */
136
+ router.get('/api/evals/runs/:id', (req, res) => {
137
+ const run = options.evalStore.load(req.params['id'] ?? '');
138
+ if (!run) {
139
+ res.status(404).json({ error: 'Run not found' });
140
+ return;
141
+ }
142
+ res.json(run);
143
+ });
144
+ /** Delete an eval run */
145
+ router.delete('/api/evals/runs/:id', (req, res) => {
146
+ const deleted = options.evalStore.delete(req.params['id'] ?? '');
147
+ if (!deleted) {
148
+ res.status(404).json({ error: 'Run not found' });
149
+ return;
150
+ }
151
+ res.json({ ok: true });
152
+ });
153
+ /** Run eval suite — SSE stream with full per-eval results */
154
+ router.post('/api/evals/run', async (req, res) => {
155
+ const port = options.getPort();
156
+ if (!port) {
157
+ res.status(503).json({ error: 'Server not ready' });
158
+ return;
159
+ }
160
+ const baseUrl = `http://127.0.0.1:${port}`;
161
+ const repo = options.sessionManager.getRepo();
162
+ const evals = repo.evals;
163
+ if (evals.length === 0) {
164
+ res.status(400).json({ error: 'No evals defined' });
165
+ return;
166
+ }
167
+ res.writeHead(200, {
168
+ 'Content-Type': 'text/event-stream',
169
+ 'Cache-Control': 'no-cache',
170
+ 'Connection': 'keep-alive',
171
+ });
172
+ const judgeProvider = createLocalJudgeProvider(baseUrl);
173
+ const modelInfo = repo.config ? {
174
+ provider: repo.config.models?.['main']?.provider ?? 'unknown',
175
+ model: repo.config.models?.['main']?.model ?? 'unknown',
176
+ } : { provider: 'unknown', model: 'unknown' };
177
+ const results = [];
178
+ const perCaseCosts = [];
179
+ const startTime = Date.now();
180
+ for (let i = 0; i < evals.length; i++) {
181
+ const ev = evals[i];
182
+ writeSSE(res, { type: 'eval_start', evalName: ev.name, current: i + 1, total: evals.length });
183
+ const evalStart = Date.now();
184
+ try {
185
+ // Run the query — streams agent events to client
186
+ const { response, toolCalls, toolResults, usage } = await streamQuery(baseUrl, ev.query, res, ev.name, ev.setup.app);
187
+ // Build enriched response for the judge — include tool results so it knows data was fetched
188
+ let enriched = response;
189
+ if (toolCalls.length > 0) {
190
+ enriched += '\n\n[Tool calls made: ' + toolCalls.map((tc) => `${tc.name}(${JSON.stringify(tc.parameters)})`).join(', ') + ']';
191
+ }
192
+ if (toolResults.length > 0) {
193
+ enriched += '\n\n[Tool results received:\n' + toolResults.join('\n') + ']';
194
+ }
195
+ // Judge assertions — track judge tokens separately
196
+ const judgeInputBefore = judgeProvider.totalInputTokens;
197
+ const judgeOutputBefore = judgeProvider.totalOutputTokens;
198
+ const assertions = await judgeAllAssertions(enriched, ev.assertions, judgeProvider);
199
+ const passed = assertions.every((a) => a.passed);
200
+ const queryCost = usage ? computeEvalCost(usage.inputTokens, usage.outputTokens, modelInfo.model) : undefined;
201
+ const judgeInputUsed = judgeProvider.totalInputTokens - judgeInputBefore;
202
+ const judgeOutputUsed = judgeProvider.totalOutputTokens - judgeOutputBefore;
203
+ const judgeCost = judgeInputUsed > 0 ? computeEvalCost(judgeInputUsed, judgeOutputUsed, modelInfo.model) : undefined;
204
+ if (queryCost)
205
+ perCaseCosts.push(queryCost);
206
+ const result = {
207
+ eval: ev,
208
+ response,
209
+ toolCalls,
210
+ assertions,
211
+ passed,
212
+ durationMs: Date.now() - evalStart,
213
+ cost: queryCost,
214
+ };
215
+ results.push(result);
216
+ // Send full result with eval_complete — separate query and judge costs
217
+ writeSSE(res, {
218
+ type: 'eval_complete',
219
+ evalName: ev.name,
220
+ passed,
221
+ current: i + 1,
222
+ total: evals.length,
223
+ result: {
224
+ response: response.length > 1000 ? response.slice(0, 1000) + '...' : response,
225
+ toolCalls,
226
+ assertions,
227
+ durationMs: result.durationMs,
228
+ queryCost,
229
+ judgeCost,
230
+ },
231
+ });
232
+ }
233
+ catch (err) {
234
+ const msg = err instanceof Error ? err.message : String(err);
235
+ const result = {
236
+ eval: ev,
237
+ response: '',
238
+ toolCalls: [],
239
+ assertions: [],
240
+ passed: false,
241
+ durationMs: Date.now() - evalStart,
242
+ error: msg,
243
+ };
244
+ results.push(result);
245
+ writeSSE(res, {
246
+ type: 'eval_complete',
247
+ evalName: ev.name,
248
+ passed: false,
249
+ current: i + 1,
250
+ total: evals.length,
251
+ result: { response: '', toolCalls: [], assertions: [], durationMs: result.durationMs, error: msg },
252
+ });
253
+ }
254
+ }
255
+ // Build suite result
256
+ const totalCost = perCaseCosts.length > 0 ? aggregateRunCost(perCaseCosts) : undefined;
257
+ const suiteResult = {
258
+ results,
259
+ totalPassed: results.filter((r) => r.passed).length,
260
+ totalFailed: results.filter((r) => !r.passed).length,
261
+ totalSkipped: 0,
262
+ totalDurationMs: Date.now() - startTime,
263
+ totalCost,
264
+ model: modelInfo,
265
+ timestamp: new Date().toISOString(),
266
+ };
267
+ const run = buildEvalRun(suiteResult, modelInfo, { orgId: 'local', triggeredBy: 'manual' });
268
+ options.evalStore.save(run); // eslint-disable-line @typescript-eslint/no-unsafe-type-assertion
269
+ writeSSE(res, { type: 'run_complete', run });
270
+ writeSSE(res, { type: 'done' });
271
+ res.end();
272
+ });
273
+ /** Get arena model config */
274
+ router.get('/api/evals/arena/models', (_req, res) => {
275
+ const repo = options.sessionManager.getRepo();
276
+ const config = repo.config;
277
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion -- config shape
278
+ const rawConfig = config;
279
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion -- config shape
280
+ const arena = rawConfig['arena'];
281
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion -- config shape
282
+ const configModels = arena?.['models'];
283
+ const models = configModels ?? [
284
+ { provider: 'anthropic', model: 'claude-sonnet-4-20250514', label: 'Claude Sonnet 4' },
285
+ { provider: 'anthropic', model: 'claude-haiku-4-5-20251001', label: 'Claude Haiku 4.5' },
286
+ { provider: 'openai', model: 'gpt-4o', label: 'GPT-4o' },
287
+ { provider: 'openai', model: 'gpt-4o-mini', label: 'GPT-4o Mini' },
288
+ { provider: 'google', model: 'gemini-2.5-pro', label: 'Gemini 2.5 Pro' },
289
+ { provider: 'google', model: 'gemini-2.5-flash', label: 'Gemini 2.5 Flash' },
290
+ ];
291
+ res.json({ models });
292
+ });
293
+ return router;
294
+ }
295
+ //# sourceMappingURL=evals.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"evals.js","sourceRoot":"","sources":["../../../../src/agent/routes/evals.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,6 @@
1
+ /**
2
+ * @license
3
+ * Copyright 2026 Amodal Labs, Inc.
4
+ * SPDX-License-Identifier: MIT
5
+ */
6
+ export {};
@@ -0,0 +1,270 @@
1
+ /**
2
+ * @license
3
+ * Copyright 2026 Amodal Labs, Inc.
4
+ * SPDX-License-Identifier: MIT
5
+ */
6
+ import { describe, it, expect, beforeEach, afterEach } from 'vitest';
7
+ import { mkdtemp, rm, readFile, stat } from 'node:fs/promises';
8
+ import path from 'node:path';
9
+ import { tmpdir } from 'node:os';
10
+ import { isAllowedRepoPath, validateRepoFilePath, executeWriteRepoFile, executeReadRepoFile, executeDeleteRepoFile, } from './agent-runner.js';
11
+ function makeAdminSession(repoOrigin) {
12
+ return {
13
+ id: 'session-1',
14
+ appId: 'admin',
15
+ conversationHistory: [],
16
+ createdAt: Date.now(),
17
+ lastAccessedAt: Date.now(),
18
+ runtime: {
19
+ repo: {
20
+ source: 'local',
21
+ origin: repoOrigin,
22
+ config: {
23
+ name: 'test',
24
+ version: '1.0.0',
25
+ models: { main: { provider: 'anthropic', model: 'claude-sonnet-4-20250514' } },
26
+ },
27
+ connections: new Map(),
28
+ skills: [],
29
+ agents: {},
30
+ automations: [],
31
+ knowledge: [],
32
+ evals: [],
33
+ tools: [],
34
+ stores: [],
35
+ },
36
+ compiledContext: {
37
+ systemPrompt: 'Admin agent.',
38
+ tokenUsage: { total: 100000, used: 100, remaining: 99900, sectionBreakdown: {} },
39
+ sections: [],
40
+ },
41
+ exploreContext: {
42
+ systemPrompt: 'Explore.',
43
+ tokenUsage: { total: 100000, used: 100, remaining: 99900, sectionBreakdown: {} },
44
+ sections: [],
45
+ },
46
+ outputPipeline: { process: (t) => ({ output: t, modified: false, blocked: false, findings: [] }) },
47
+ telemetry: { logGuard: () => { } },
48
+ },
49
+ planModeManager: { isActive: () => false, getStatus: () => ({ active: false }) },
50
+ };
51
+ }
52
+ function makeNonAdminSession(repoOrigin) {
53
+ const session = makeAdminSession(repoOrigin);
54
+ return { ...session, appId: 'app-1' };
55
+ }
56
+ describe('isAllowedRepoPath', () => {
57
+ it('allows skills/ paths', () => {
58
+ expect(isAllowedRepoPath('skills/triage/SKILL.md')).toBe(true);
59
+ expect(isAllowedRepoPath('skills/new-skill/SKILL.md')).toBe(true);
60
+ });
61
+ it('allows knowledge/ paths', () => {
62
+ expect(isAllowedRepoPath('knowledge/formatting-rules.md')).toBe(true);
63
+ expect(isAllowedRepoPath('knowledge/deep/nested/doc.md')).toBe(true);
64
+ });
65
+ it('allows connections/ paths', () => {
66
+ expect(isAllowedRepoPath('connections/slack/rules.md')).toBe(true);
67
+ expect(isAllowedRepoPath('connections/github/surface.md')).toBe(true);
68
+ expect(isAllowedRepoPath('connections/stripe/entities.md')).toBe(true);
69
+ expect(isAllowedRepoPath('connections/slack/spec.json')).toBe(true);
70
+ expect(isAllowedRepoPath('connections/slack/access.json')).toBe(true);
71
+ });
72
+ it('allows pages/ paths', () => {
73
+ expect(isAllowedRepoPath('pages/dashboard.tsx')).toBe(true);
74
+ expect(isAllowedRepoPath('pages/price-tracker.tsx')).toBe(true);
75
+ });
76
+ it('allows automations/ paths', () => {
77
+ expect(isAllowedRepoPath('automations/daily-digest.json')).toBe(true);
78
+ });
79
+ it('allows stores/ paths', () => {
80
+ expect(isAllowedRepoPath('stores/price-data.json')).toBe(true);
81
+ });
82
+ it('allows tools/ paths', () => {
83
+ expect(isAllowedRepoPath('tools/fetch-prices/tool.json')).toBe(true);
84
+ expect(isAllowedRepoPath('tools/fetch-prices/handler.ts')).toBe(true);
85
+ });
86
+ it('allows evals/ paths', () => {
87
+ expect(isAllowedRepoPath('evals/triage-accuracy.md')).toBe(true);
88
+ });
89
+ it('allows agents/ paths', () => {
90
+ expect(isAllowedRepoPath('agents/explore/AGENT.md')).toBe(true);
91
+ });
92
+ it('rejects top-level files', () => {
93
+ expect(isAllowedRepoPath('amodal.json')).toBe(false);
94
+ expect(isAllowedRepoPath('package.json')).toBe(false);
95
+ });
96
+ it('rejects src/ paths', () => {
97
+ expect(isAllowedRepoPath('src/index.ts')).toBe(false);
98
+ });
99
+ it('rejects .env', () => {
100
+ expect(isAllowedRepoPath('.env')).toBe(false);
101
+ });
102
+ it('rejects blocked filenames even inside allowed dirs', () => {
103
+ expect(isAllowedRepoPath('tools/.env')).toBe(false);
104
+ expect(isAllowedRepoPath('pages/amodal.json')).toBe(false);
105
+ expect(isAllowedRepoPath('skills/package.json')).toBe(false);
106
+ });
107
+ });
108
+ describe('validateRepoFilePath', () => {
109
+ it('rejects absolute paths', () => {
110
+ const session = makeAdminSession('/tmp/repo');
111
+ const result = validateRepoFilePath(session, '/etc/passwd');
112
+ expect(result).toHaveProperty('error');
113
+ expect(result.error).toContain('relative');
114
+ });
115
+ it('rejects path traversal', () => {
116
+ const session = makeAdminSession('/tmp/repo');
117
+ const result = validateRepoFilePath(session, '../../etc/passwd');
118
+ expect(result).toHaveProperty('error');
119
+ expect(result.error).toContain('traversal');
120
+ });
121
+ it('rejects non-admin sessions', () => {
122
+ const session = makeNonAdminSession('/tmp/repo');
123
+ const result = validateRepoFilePath(session, 'knowledge/foo.md');
124
+ expect(result).toHaveProperty('error');
125
+ expect(result.error).toContain('admin');
126
+ });
127
+ it('rejects disallowed directories', () => {
128
+ const session = makeAdminSession('/tmp/repo');
129
+ const result = validateRepoFilePath(session, 'src/index.ts');
130
+ expect(result).toHaveProperty('error');
131
+ expect(result.error).toContain('not in an allowed directory');
132
+ });
133
+ it('accepts valid knowledge path', () => {
134
+ const session = makeAdminSession('/tmp/repo');
135
+ const result = validateRepoFilePath(session, 'knowledge/formatting-rules.md');
136
+ expect(result).toHaveProperty('resolved');
137
+ expect(result.resolved).toBe(path.resolve('/tmp/repo', 'knowledge/formatting-rules.md'));
138
+ });
139
+ });
140
+ describe('executeWriteRepoFile', () => {
141
+ let tmpDir;
142
+ beforeEach(async () => {
143
+ tmpDir = await mkdtemp(path.join(tmpdir(), 'amodal-test-'));
144
+ });
145
+ afterEach(async () => {
146
+ await rm(tmpDir, { recursive: true, force: true });
147
+ });
148
+ it('writes a knowledge file', async () => {
149
+ const session = makeAdminSession(tmpDir);
150
+ const result = await executeWriteRepoFile(session, {
151
+ path: 'knowledge/no-em-dashes.md',
152
+ content: '# Formatting Rules\n\nNever use em dashes in output.',
153
+ });
154
+ expect(result.output).toContain('Wrote knowledge/no-em-dashes.md');
155
+ const written = await readFile(path.join(tmpDir, 'knowledge/no-em-dashes.md'), 'utf-8');
156
+ expect(written).toContain('Never use em dashes');
157
+ });
158
+ it('writes a skill file with nested directories', async () => {
159
+ const session = makeAdminSession(tmpDir);
160
+ const result = await executeWriteRepoFile(session, {
161
+ path: 'skills/formatting/SKILL.md',
162
+ content: '# Formatting Skill',
163
+ });
164
+ expect(result.output).toContain('Wrote skills/formatting/SKILL.md');
165
+ });
166
+ it('writes a page file', async () => {
167
+ const session = makeAdminSession(tmpDir);
168
+ const result = await executeWriteRepoFile(session, {
169
+ path: 'pages/price-dashboard.tsx',
170
+ content: 'export default function PriceDashboard() { return <div>Prices</div>; }',
171
+ });
172
+ expect(result.output).toContain('Wrote pages/price-dashboard.tsx');
173
+ });
174
+ it('writes an automation file', async () => {
175
+ const session = makeAdminSession(tmpDir);
176
+ const result = await executeWriteRepoFile(session, {
177
+ path: 'automations/fetch-prices.json',
178
+ content: JSON.stringify({ title: 'Fetch Prices', schedule: '0 * * * *', prompt: 'Fetch latest prices' }),
179
+ });
180
+ expect(result.output).toContain('Wrote automations/fetch-prices.json');
181
+ });
182
+ it('rejects writes to disallowed paths', async () => {
183
+ const session = makeAdminSession(tmpDir);
184
+ const result = await executeWriteRepoFile(session, {
185
+ path: 'src/index.ts',
186
+ content: 'console.log("pwned")',
187
+ });
188
+ expect(result.error).toContain('not in an allowed directory');
189
+ });
190
+ it('rejects empty content', async () => {
191
+ const session = makeAdminSession(tmpDir);
192
+ const result = await executeWriteRepoFile(session, {
193
+ path: 'knowledge/test.md',
194
+ content: '',
195
+ });
196
+ expect(result.error).toContain('empty');
197
+ });
198
+ it('rejects path traversal', async () => {
199
+ const session = makeAdminSession(tmpDir);
200
+ const result = await executeWriteRepoFile(session, {
201
+ path: '../../../etc/passwd',
202
+ content: 'bad',
203
+ });
204
+ expect(result.error).toContain('traversal');
205
+ });
206
+ });
207
+ describe('executeReadRepoFile', () => {
208
+ let tmpDir;
209
+ beforeEach(async () => {
210
+ tmpDir = await mkdtemp(path.join(tmpdir(), 'amodal-test-'));
211
+ });
212
+ afterEach(async () => {
213
+ await rm(tmpDir, { recursive: true, force: true });
214
+ });
215
+ it('reads an existing knowledge file', async () => {
216
+ const session = makeAdminSession(tmpDir);
217
+ // Write a file first
218
+ await executeWriteRepoFile(session, {
219
+ path: 'knowledge/test.md',
220
+ content: 'Test content here.',
221
+ });
222
+ const result = await executeReadRepoFile(session, { path: 'knowledge/test.md' });
223
+ expect(result.output).toBe('Test content here.');
224
+ });
225
+ it('returns error for missing file', async () => {
226
+ const session = makeAdminSession(tmpDir);
227
+ const result = await executeReadRepoFile(session, { path: 'knowledge/missing.md' });
228
+ expect(result.error).toContain('not found');
229
+ });
230
+ it('rejects reads from disallowed paths', async () => {
231
+ const session = makeAdminSession(tmpDir);
232
+ const result = await executeReadRepoFile(session, { path: 'package.json' });
233
+ expect(result.error).toContain('not in an allowed directory');
234
+ });
235
+ });
236
+ describe('executeDeleteRepoFile', () => {
237
+ let tmpDir;
238
+ beforeEach(async () => {
239
+ tmpDir = await mkdtemp(path.join(tmpdir(), 'amodal-test-'));
240
+ });
241
+ afterEach(async () => {
242
+ await rm(tmpDir, { recursive: true, force: true });
243
+ });
244
+ it('deletes an existing file', async () => {
245
+ const session = makeAdminSession(tmpDir);
246
+ await executeWriteRepoFile(session, {
247
+ path: 'evals/old-test.md',
248
+ content: 'old eval',
249
+ });
250
+ const result = await executeDeleteRepoFile(session, { path: 'evals/old-test.md' });
251
+ expect(result.output).toContain('Deleted evals/old-test.md');
252
+ await expect(stat(path.join(tmpDir, 'evals/old-test.md'))).rejects.toThrow();
253
+ });
254
+ it('returns error for missing file', async () => {
255
+ const session = makeAdminSession(tmpDir);
256
+ const result = await executeDeleteRepoFile(session, { path: 'evals/nonexistent.md' });
257
+ expect(result.error).toContain('not found');
258
+ });
259
+ it('rejects deletes from disallowed paths', async () => {
260
+ const session = makeAdminSession(tmpDir);
261
+ const result = await executeDeleteRepoFile(session, { path: 'package.json' });
262
+ expect(result.error).toContain('not in an allowed directory');
263
+ });
264
+ it('rejects path traversal', async () => {
265
+ const session = makeAdminSession(tmpDir);
266
+ const result = await executeDeleteRepoFile(session, { path: '../../etc/passwd' });
267
+ expect(result.error).toContain('traversal');
268
+ });
269
+ });
270
+ //# sourceMappingURL=write-repo-file.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"write-repo-file.test.js","sourceRoot":"","sources":["../../../src/agent/write-repo-file.test.ts"],"names":[],"mappings":"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"}