boxsafe 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/.directory +2 -0
- package/.env.example +3 -0
- package/AUDIT_LANG.md +45 -0
- package/BOXSAFE_VERSION_NOTES.md +14 -0
- package/README.md +4 -0
- package/TODO.md +130 -0
- package/adapters/index.ts +27 -0
- package/adapters/primary/cli-adapter.ts +56 -0
- package/adapters/secondary/filesystem/node-filesystem.ts +307 -0
- package/adapters/secondary/system/configuration.ts +147 -0
- package/ai/caller.ts +42 -0
- package/ai/label.ts +33 -0
- package/ai/modelConfig.ts +236 -0
- package/ai/provider.ts +111 -0
- package/boxsafe.config.json +68 -0
- package/core/auth/dasktop/cred/CRED.md +112 -0
- package/core/auth/dasktop/cred/credLinux.ts +82 -0
- package/core/auth/dasktop/cred/credWin.ts +2 -0
- package/core/config/defaults/boxsafeDefaults.ts +67 -0
- package/core/config/defaults/index.ts +1 -0
- package/core/config/loadConfig.ts +133 -0
- package/core/loop/about.md +13 -0
- package/core/loop/boxConfig.ts +20 -0
- package/core/loop/buildExecCommand.ts +76 -0
- package/core/loop/cmd/execode.ts +121 -0
- package/core/loop/cmd/test.js +3 -0
- package/core/loop/execLoop.ts +341 -0
- package/core/loop/git/VERSIONING.md +17 -0
- package/core/loop/git/commands.ts +11 -0
- package/core/loop/git/gitClient.ts +78 -0
- package/core/loop/git/index.ts +99 -0
- package/core/loop/git/runVersionControlRunner.ts +33 -0
- package/core/loop/initNavigator.ts +44 -0
- package/core/loop/initTasksManager.ts +35 -0
- package/core/loop/runValidation.ts +25 -0
- package/core/loop/tasks/AGENT-TASKS.md +36 -0
- package/core/loop/tasks/index.ts +96 -0
- package/core/loop/toolCalls.ts +168 -0
- package/core/loop/toolDispatcher.ts +146 -0
- package/core/loop/traceLogger.ts +106 -0
- package/core/loop/types.ts +26 -0
- package/core/loop/versionControlAdapter.ts +36 -0
- package/core/loop/waterfall.ts +404 -0
- package/core/loop/writeArtifactAtomically.ts +13 -0
- package/core/navigate/NAVIGATE.md +186 -0
- package/core/navigate/about.md +128 -0
- package/core/navigate/examples.ts +367 -0
- package/core/navigate/handler.ts +148 -0
- package/core/navigate/index.ts +32 -0
- package/core/navigate/navigate.test.ts +372 -0
- package/core/navigate/navigator.ts +437 -0
- package/core/navigate/types.ts +132 -0
- package/core/navigate/utils.ts +146 -0
- package/core/paths/paths.ts +33 -0
- package/core/ports/index.ts +271 -0
- package/core/segments/CONVENTIONS.md +30 -0
- package/core/segments/loop/index.ts +18 -0
- package/core/segments/map.ts +56 -0
- package/core/segments/navigate/index.ts +20 -0
- package/core/segments/versionControl/index.ts +18 -0
- package/core/util/logger.ts +128 -0
- package/docs/AGENT-TASKS.md +36 -0
- package/docs/ARQUITETURA_CORRECAO.md +121 -0
- package/docs/CONVENTIONS.md +30 -0
- package/docs/CRED.md +112 -0
- package/docs/L_RAG.md +567 -0
- package/docs/NAVIGATE.md +186 -0
- package/docs/PRIMARY_ACTORS.md +78 -0
- package/docs/SECONDARY_ACTORS.md +174 -0
- package/docs/VERSIONING.md +17 -0
- package/docs/boxsafe.config.md +472 -0
- package/eslint.config.mts +15 -0
- package/main.ts +53 -0
- package/memo/generated/codelog.md +13 -0
- package/memo/state/tasks/state.json +6 -0
- package/memo/state/tasks/tasks/task_001.md +2 -0
- package/memo/states-logs/logs.txt +7 -0
- package/memo/states-logs/trace-mljvrxvi-9g0k4q.jsonl +11 -0
- package/memo/states-logs/trace-mljvvc9j-pe9ekj.jsonl +11 -0
- package/memo/states-logs/trace-mljvvm1c-wbnqzp.jsonl +11 -0
- package/memo/states-logs/trace-mljxecwn-9xh3nw.jsonl +11 -0
- package/memo/states-logs/trace-mljxqkfm-ipijik.jsonl +11 -0
- package/memo/states-logs/trace-mljxwtrw-3fanky.jsonl +11 -0
- package/memo/states-logs/trace-mljxzen3-m8iinh.jsonl +11 -0
- package/memo/states-logs/trace-mljyucef-td6odn.jsonl +11 -0
- package/memo/states-logs/trace-mljyuprw-b1a6f4.jsonl +11 -0
- package/memo/states-logs/trace-mljyvefl-b6yoce.jsonl +11 -0
- package/memo/states-logs/trace-mljyxjo4-n7ibj2.jsonl +13 -0
- package/memo/states-logs/trace-mljziez5-8drqtn.jsonl +13 -0
- package/memo/states-logs/trace-mljziulp-dtd03z.jsonl +13 -0
- package/memo/states-logs/trace-mljzjwrq-1p2krb.jsonl +13 -0
- package/memo/states-logs/trace-mljzl0i7-b1cqa6.jsonl +13 -0
- package/memo/states-logs/trace-mljzmlk6-7kdyls.jsonl +13 -0
- package/memo/states-logs/trace-mlk0oj25-xa3dcu.jsonl +13 -0
- package/memo/states-logs/trace-mlk1x59q-713huj.jsonl +14 -0
- package/memo/states-logs/trace-mlk22dz8-7fd6hq.jsonl +14 -0
- package/memo/states-logs/trace-mlk241uy-wmx907.jsonl +14 -0
- package/memo/states-logs/trace-mlk2bf5r-yoh1vg.jsonl +15 -0
- package/package.json +44 -0
- package/pnpm-workspace.yaml +4 -0
- package/prompt_improvement_example.md +55 -0
- package/remove.txt +1 -0
- package/tests/adapters.test.ts +128 -0
- package/tests/extractCode.test.ts +26 -0
- package/tests/integration.test.ts +83 -0
- package/tests/loadConfig.test.ts +25 -0
- package/tests/navigatorBoundary.test.ts +17 -0
- package/tests/ports.test.ts +84 -0
- package/tests/runAllTests.ts +49 -0
- package/tests/toolCalls.test.ts +149 -0
- package/tests/waterfall.test.ts +52 -0
- package/tsconfig.json +32 -0
- package/tsup.config.ts +17 -0
- package/types.d.ts +96 -0
- package/util/ANSI.ts +29 -0
- package/util/extractCode.ts +217 -0
- package/util/extractToolCalls.ts +80 -0
- package/util/logger.ts +125 -0
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import assert from 'node:assert/strict';
|
|
2
|
+
import { test } from './runAllTests';
|
|
3
|
+
import path from 'node:path';
|
|
4
|
+
import fs from 'node:fs';
|
|
5
|
+
import { loadBoxSafeConfig } from '@core/config/loadConfig';
|
|
6
|
+
|
|
7
|
+
test('loadBoxSafeConfig: falls back to defaults when file does not exist', async () => {
|
|
8
|
+
const res = loadBoxSafeConfig('/tmp/__boxsafe_config_does_not_exist__.json');
|
|
9
|
+
assert.equal(res.source.loaded, false);
|
|
10
|
+
assert.ok(res.config);
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
test('loadBoxSafeConfig: normalizes limits.loops from "infinity" to MAX_SAFE_INTEGER', async () => {
|
|
14
|
+
const tmpDir = fs.mkdtempSync(path.join('/tmp', 'cfg-'));
|
|
15
|
+
const cfgPath = path.join(tmpDir, 'boxsafe.config.json');
|
|
16
|
+
fs.writeFileSync(cfgPath, JSON.stringify({ limits: { loops: 'infinity' } }), 'utf-8');
|
|
17
|
+
|
|
18
|
+
try {
|
|
19
|
+
const res = loadBoxSafeConfig(cfgPath);
|
|
20
|
+
assert.equal(res.source.loaded, true);
|
|
21
|
+
assert.equal(res.config.limits?.loops, Number.MAX_SAFE_INTEGER);
|
|
22
|
+
} finally {
|
|
23
|
+
fs.rmSync(tmpDir, { recursive: true, force: true });
|
|
24
|
+
}
|
|
25
|
+
});
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import assert from 'node:assert/strict';
|
|
2
|
+
import { test } from './runAllTests';
|
|
3
|
+
import path from 'node:path';
|
|
4
|
+
import fs from 'node:fs';
|
|
5
|
+
import { createNavigator } from '@core/navigate';
|
|
6
|
+
|
|
7
|
+
test('Navigator: blocks directory traversal outside workspace', async () => {
|
|
8
|
+
const tempDir = fs.mkdtempSync(path.join('/tmp', 'nav-boundary-'));
|
|
9
|
+
try {
|
|
10
|
+
const nav = createNavigator({ workspace: tempDir });
|
|
11
|
+
const res = await nav.readFile('../../../etc/passwd');
|
|
12
|
+
assert.equal(res.ok, false);
|
|
13
|
+
assert.ok(String((res as any).error).includes('outside workspace'));
|
|
14
|
+
} finally {
|
|
15
|
+
fs.rmSync(tempDir, { recursive: true, force: true });
|
|
16
|
+
}
|
|
17
|
+
});
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import assert from 'node:assert/strict';
|
|
2
|
+
import { test } from './runAllTests';
|
|
3
|
+
import type {
|
|
4
|
+
ISystemConfigurationPort,
|
|
5
|
+
IFileSystemPort,
|
|
6
|
+
ISystemExecutionPort
|
|
7
|
+
} from '@core/ports';
|
|
8
|
+
|
|
9
|
+
test('Ports: ISystemConfigurationPort interface exists', () => {
|
|
10
|
+
// This test ensures the port interface is properly defined
|
|
11
|
+
// We'll test the actual interface structure by checking if it has the expected methods
|
|
12
|
+
const portMethods = ['loadConfiguration', 'validateConfiguration'];
|
|
13
|
+
|
|
14
|
+
// Test that we can import the type
|
|
15
|
+
type ConfigPort = ISystemConfigurationPort;
|
|
16
|
+
|
|
17
|
+
// Test that the methods are expected (this will fail if interface changes)
|
|
18
|
+
const mockPort: Partial<ConfigPort> = {
|
|
19
|
+
loadConfiguration: async () => ({ config: {} as any, source: { path: '', loaded: false } }),
|
|
20
|
+
validateConfiguration: async () => ({ valid: true, errors: [], warnings: [] })
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
assert.equal(typeof mockPort.loadConfiguration, 'function');
|
|
24
|
+
assert.equal(typeof mockPort.validateConfiguration, 'function');
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
test('Ports: IFileSystemPort interface exists', () => {
|
|
28
|
+
const portMethods = ['listDirectory', 'readFile', 'writeFile', 'createDirectory', 'delete', 'getMetadata'];
|
|
29
|
+
|
|
30
|
+
type FsPort = IFileSystemPort;
|
|
31
|
+
|
|
32
|
+
const mockPort: Partial<FsPort> = {
|
|
33
|
+
listDirectory: async () => ({ ok: true }),
|
|
34
|
+
readFile: async () => ({ ok: true }),
|
|
35
|
+
writeFile: async () => ({ ok: true }),
|
|
36
|
+
createDirectory: async () => ({ ok: true }),
|
|
37
|
+
delete: async () => ({ ok: true }),
|
|
38
|
+
getMetadata: async () => ({ ok: true })
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
assert.equal(typeof mockPort.listDirectory, 'function');
|
|
42
|
+
assert.equal(typeof mockPort.readFile, 'function');
|
|
43
|
+
assert.equal(typeof mockPort.writeFile, 'function');
|
|
44
|
+
assert.equal(typeof mockPort.createDirectory, 'function');
|
|
45
|
+
assert.equal(typeof mockPort.delete, 'function');
|
|
46
|
+
assert.equal(typeof mockPort.getMetadata, 'function');
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
test('Ports: ISystemExecutionPort interface exists', () => {
|
|
50
|
+
type ExecPort = ISystemExecutionPort;
|
|
51
|
+
|
|
52
|
+
const mockPort: Partial<ExecPort> = {
|
|
53
|
+
executeSegment: async () => ({}),
|
|
54
|
+
listSegments: () => ({})
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
assert.equal(typeof mockPort.executeSegment, 'function');
|
|
58
|
+
assert.equal(typeof mockPort.listSegments, 'function');
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
test('Ports: ConfigurationResult type structure', () => {
|
|
62
|
+
// Test that the types are properly structured
|
|
63
|
+
const result = {
|
|
64
|
+
config: {},
|
|
65
|
+
source: { path: '/test', loaded: true }
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
assert.ok(result.config);
|
|
69
|
+
assert.equal(result.source.path, '/test');
|
|
70
|
+
assert.equal(result.source.loaded, true);
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
test('Ports: ValidationResult type structure', () => {
|
|
74
|
+
const result = {
|
|
75
|
+
valid: true,
|
|
76
|
+
errors: [],
|
|
77
|
+
warnings: ['test warning']
|
|
78
|
+
};
|
|
79
|
+
|
|
80
|
+
assert.equal(result.valid, true);
|
|
81
|
+
assert.ok(Array.isArray(result.errors));
|
|
82
|
+
assert.ok(Array.isArray(result.warnings));
|
|
83
|
+
assert.equal(result.warnings[0], 'test warning');
|
|
84
|
+
});
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import assert from 'node:assert/strict';
|
|
2
|
+
import { Logger } from '@util/logger';
|
|
3
|
+
|
|
4
|
+
const logger = Logger.createModuleLogger('TestRunner');
|
|
5
|
+
|
|
6
|
+
type TestFn = () => void | Promise<void>;
|
|
7
|
+
|
|
8
|
+
const tests: Array<{ name: string; fn: TestFn }> = [];
|
|
9
|
+
|
|
10
|
+
export function test(name: string, fn: TestFn) {
|
|
11
|
+
tests.push({ name, fn });
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
async function run() {
|
|
15
|
+
let passed = 0;
|
|
16
|
+
let failed = 0;
|
|
17
|
+
|
|
18
|
+
const files = [
|
|
19
|
+
'./tests/ports.test.ts',
|
|
20
|
+
'./tests/adapters.test.ts',
|
|
21
|
+
'./tests/extractCode.test.ts',
|
|
22
|
+
'./tests/toolCalls.test.ts',
|
|
23
|
+
'./tests/integration.test.ts',
|
|
24
|
+
'./tests/navigatorBoundary.test.ts',
|
|
25
|
+
'./tests/waterfall.test.ts',
|
|
26
|
+
'./tests/loadConfig.test.ts',
|
|
27
|
+
];
|
|
28
|
+
|
|
29
|
+
for (const f of files) {
|
|
30
|
+
await import(new URL(f, `file://${process.cwd()}/`).toString());
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
for (const t of tests) {
|
|
34
|
+
try {
|
|
35
|
+
await t.fn();
|
|
36
|
+
passed++;
|
|
37
|
+
logger.info(`✓ ${t.name}`);
|
|
38
|
+
} catch (err: any) {
|
|
39
|
+
failed++;
|
|
40
|
+
logger.error(`✗ ${t.name}`);
|
|
41
|
+
logger.error(String(err?.stack ?? err));
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
logger.info(`\nResults: ${passed} passed, ${failed} failed`);
|
|
46
|
+
assert.equal(failed, 0);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
run();
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
import assert from 'node:assert/strict';
|
|
2
|
+
import { test } from './runAllTests';
|
|
3
|
+
import { parseToolCallsFromMarkdown } from '@/core/loop/toolCalls';
|
|
4
|
+
|
|
5
|
+
test('parseToolCallsFromMarkdown: parses valid json-tool write operation', async () => {
|
|
6
|
+
const md = '```json-tool\n{"tool": "navigate", "params": {"op": "write", "path": "test.ts", "content": "console.log(\\"hello\\");"}}\n```';
|
|
7
|
+
const result = parseToolCallsFromMarkdown(md);
|
|
8
|
+
|
|
9
|
+
assert.equal(result.ok, true);
|
|
10
|
+
assert.equal(result.calls.length, 1);
|
|
11
|
+
assert.equal(result.errors.length, 0);
|
|
12
|
+
|
|
13
|
+
const call = result.calls[0];
|
|
14
|
+
if (!call) throw new Error('Call should be defined');
|
|
15
|
+
assert.equal(call.tool, 'navigate');
|
|
16
|
+
assert.equal(call.params.op, 'write');
|
|
17
|
+
assert.equal(call.params.path, 'test.ts');
|
|
18
|
+
assert.equal(call.params.content, 'console.log("hello");');
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
test('parseToolCallsFromMarkdown: handles incomplete json-tool write operation', async () => {
|
|
22
|
+
const md = '```json-tool\n{"tool": "navigate", "params": {"op": "write", "path": "test.ts"}}\n```';
|
|
23
|
+
const result = parseToolCallsFromMarkdown(md);
|
|
24
|
+
|
|
25
|
+
assert.equal(result.ok, true);
|
|
26
|
+
assert.equal(result.calls.length, 0);
|
|
27
|
+
assert.equal(result.errors.length, 1);
|
|
28
|
+
|
|
29
|
+
const error = result.errors[0];
|
|
30
|
+
if (!error) throw new Error('Error should be defined');
|
|
31
|
+
assert.equal(error.ok, false);
|
|
32
|
+
assert.match(error.error, /navigate\.op=write requires params\.content/);
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
test('parseToolCallsFromMarkdown: parses multiple json-tool blocks', async () => {
|
|
36
|
+
const md = `
|
|
37
|
+
\`\`\`json-tool
|
|
38
|
+
{"tool": "navigate", "params": {"op": "read", "path": "file.ts"}}
|
|
39
|
+
\`\`\`
|
|
40
|
+
|
|
41
|
+
Some text here
|
|
42
|
+
|
|
43
|
+
\`\`\`json-tool
|
|
44
|
+
{"tool": "navigate", "params": {"op": "write", "path": "out.ts", "content": "export const x = 1;"}}
|
|
45
|
+
\`\`\`
|
|
46
|
+
`;
|
|
47
|
+
|
|
48
|
+
const result = parseToolCallsFromMarkdown(md);
|
|
49
|
+
|
|
50
|
+
assert.equal(result.ok, true);
|
|
51
|
+
assert.equal(result.calls.length, 2);
|
|
52
|
+
assert.equal(result.errors.length, 0);
|
|
53
|
+
|
|
54
|
+
const call1 = result.calls[0];
|
|
55
|
+
const call2 = result.calls[1];
|
|
56
|
+
if (!call1 || !call2) throw new Error('Calls should be defined');
|
|
57
|
+
|
|
58
|
+
assert.equal(call1.params.op, 'read');
|
|
59
|
+
assert.equal(call2.params.op, 'write');
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
test('parseToolCallsFromMarkdown: ignores regular json blocks', async () => {
|
|
63
|
+
const md = `
|
|
64
|
+
\`\`\`json
|
|
65
|
+
{"tool": "navigate", "params": {"op": "write", "path": "test.ts", "content": "hello"}}
|
|
66
|
+
\`\`\`
|
|
67
|
+
|
|
68
|
+
\`\`\`json-tool
|
|
69
|
+
{"tool": "navigate", "params": {"op": "read", "path": "file.ts"}}
|
|
70
|
+
\`\`\`
|
|
71
|
+
`;
|
|
72
|
+
|
|
73
|
+
const result = parseToolCallsFromMarkdown(md);
|
|
74
|
+
|
|
75
|
+
assert.equal(result.ok, true);
|
|
76
|
+
assert.equal(result.calls.length, 1);
|
|
77
|
+
const call = result.calls[0];
|
|
78
|
+
if (!call) throw new Error('Call should be defined');
|
|
79
|
+
assert.equal(call.params.op, 'read');
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
test('parseToolCallsFromMarkdown: handles invalid JSON', async () => {
|
|
83
|
+
const md = '```json-tool\n{"tool": "navigate", "params": {"op": "write", "path": "test.ts", "content": }\n```';
|
|
84
|
+
const result = parseToolCallsFromMarkdown(md);
|
|
85
|
+
|
|
86
|
+
assert.equal(result.ok, true);
|
|
87
|
+
assert.equal(result.calls.length, 0);
|
|
88
|
+
assert.equal(result.errors.length, 1);
|
|
89
|
+
|
|
90
|
+
const error = result.errors[0];
|
|
91
|
+
if (!error) throw new Error('Error should be defined');
|
|
92
|
+
assert.equal(error.ok, false);
|
|
93
|
+
assert.match(error.error, /invalid JSON/);
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
test('parseToolCallsFromMarkdown: handles empty markdown', async () => {
|
|
97
|
+
const md = '';
|
|
98
|
+
const result = parseToolCallsFromMarkdown(md);
|
|
99
|
+
|
|
100
|
+
assert.equal(result.ok, true);
|
|
101
|
+
assert.equal(result.calls.length, 0);
|
|
102
|
+
assert.equal(result.errors.length, 0);
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
test('parseToolCallsFromMarkdown: handles markdown without json-tool blocks', async () => {
|
|
106
|
+
const md = `
|
|
107
|
+
# Some markdown
|
|
108
|
+
|
|
109
|
+
\`\`\`ts
|
|
110
|
+
console.log("hello");
|
|
111
|
+
\`\`\`
|
|
112
|
+
|
|
113
|
+
Some text here
|
|
114
|
+
`;
|
|
115
|
+
|
|
116
|
+
const result = parseToolCallsFromMarkdown(md);
|
|
117
|
+
|
|
118
|
+
assert.equal(result.ok, true);
|
|
119
|
+
assert.equal(result.calls.length, 0);
|
|
120
|
+
assert.equal(result.errors.length, 0);
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
test('parseToolCallsFromMarkdown: handles versionControl tool', async () => {
|
|
124
|
+
const md = '```json-tool\n{"tool": "versionControl", "params": {"autoPush": true}}\n```';
|
|
125
|
+
const result = parseToolCallsFromMarkdown(md);
|
|
126
|
+
|
|
127
|
+
assert.equal(result.ok, true);
|
|
128
|
+
assert.equal(result.calls.length, 1);
|
|
129
|
+
assert.equal(result.errors.length, 0);
|
|
130
|
+
|
|
131
|
+
const call = result.calls[0];
|
|
132
|
+
if (!call) throw new Error('Call should be defined');
|
|
133
|
+
assert.equal(call.tool, 'versionControl');
|
|
134
|
+
assert.equal((call.params as any).autoPush, true);
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
test('parseToolCallsFromMarkdown: handles malformed json-tool block', async () => {
|
|
138
|
+
const md = '```json-tool\nnot a json\n```';
|
|
139
|
+
const result = parseToolCallsFromMarkdown(md);
|
|
140
|
+
|
|
141
|
+
assert.equal(result.ok, true);
|
|
142
|
+
assert.equal(result.calls.length, 0);
|
|
143
|
+
assert.equal(result.errors.length, 1);
|
|
144
|
+
|
|
145
|
+
const error = result.errors[0];
|
|
146
|
+
if (!error) throw new Error('Error should be defined');
|
|
147
|
+
assert.equal(error.ok, false);
|
|
148
|
+
assert.match(error.error, /invalid JSON/);
|
|
149
|
+
});
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import assert from 'node:assert/strict';
|
|
2
|
+
import { test } from './runAllTests';
|
|
3
|
+
import { waterfall } from '@core/loop/waterfall';
|
|
4
|
+
import fs from 'node:fs';
|
|
5
|
+
import path from 'node:path';
|
|
6
|
+
|
|
7
|
+
test('waterfall: fails critical on non-zero exit code', async () => {
|
|
8
|
+
const res = await waterfall({
|
|
9
|
+
exec: { exitCode: 1, stdout: '', stderr: '' },
|
|
10
|
+
artifacts: {},
|
|
11
|
+
});
|
|
12
|
+
assert.equal(res.ok, false);
|
|
13
|
+
assert.equal((res as any).layer, 'exit-code');
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
test('waterfall: passes with exitCode=0 and empty stderr (contracts disabled)', async () => {
|
|
17
|
+
const prev = process.env.SUCCESS_CONTRACTS;
|
|
18
|
+
process.env.SUCCESS_CONTRACTS = '';
|
|
19
|
+
try {
|
|
20
|
+
const res = await waterfall({
|
|
21
|
+
exec: { exitCode: 0, stdout: '', stderr: '' },
|
|
22
|
+
artifacts: {},
|
|
23
|
+
});
|
|
24
|
+
assert.equal(res.ok, true);
|
|
25
|
+
assert.ok(res.score >= 70);
|
|
26
|
+
} finally {
|
|
27
|
+
if (prev === undefined) delete process.env.SUCCESS_CONTRACTS;
|
|
28
|
+
else process.env.SUCCESS_CONTRACTS = prev;
|
|
29
|
+
}
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
test('waterfall: artifacts check fails when outputFile is empty', async () => {
|
|
33
|
+
const tmpDir = fs.mkdtempSync(path.join('/tmp', 'wf-'));
|
|
34
|
+
const out = path.join(tmpDir, 'out.txt');
|
|
35
|
+
fs.writeFileSync(out, '');
|
|
36
|
+
|
|
37
|
+
const prev = process.env.SUCCESS_CONTRACTS;
|
|
38
|
+
process.env.SUCCESS_CONTRACTS = '';
|
|
39
|
+
try {
|
|
40
|
+
const res = await waterfall({
|
|
41
|
+
exec: { exitCode: 0, stdout: '', stderr: '' },
|
|
42
|
+
artifacts: { outputFile: out },
|
|
43
|
+
});
|
|
44
|
+
// Artifacts layer is non-critical; score should still be computed.
|
|
45
|
+
assert.equal(res.ok, true);
|
|
46
|
+
assert.equal(res.breakdown.artifacts.passed, false);
|
|
47
|
+
} finally {
|
|
48
|
+
if (prev === undefined) delete process.env.SUCCESS_CONTRACTS;
|
|
49
|
+
else process.env.SUCCESS_CONTRACTS = prev;
|
|
50
|
+
fs.rmSync(tmpDir, { recursive: true, force: true });
|
|
51
|
+
}
|
|
52
|
+
});
|
package/tsconfig.json
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"module": "ESNext",
|
|
4
|
+
"moduleResolution": "bundler",
|
|
5
|
+
"target": "esnext",
|
|
6
|
+
"paths": {
|
|
7
|
+
"@/*": ["./*"],
|
|
8
|
+
"@core/*": ["./core/*"],
|
|
9
|
+
"@coreutil/*": ["./core/util/*"],
|
|
10
|
+
"@ai/*": ["./ai/*"],
|
|
11
|
+
"@memo/*": ["./memo/*"],
|
|
12
|
+
"@util/*": ["./util/*"],
|
|
13
|
+
"@adapters/*": ["./adapters/*"]
|
|
14
|
+
},
|
|
15
|
+
"allowJs": true,
|
|
16
|
+
"types": ["node"],
|
|
17
|
+
"sourceMap": true,
|
|
18
|
+
"declaration": true,
|
|
19
|
+
"declarationMap": true,
|
|
20
|
+
"noUncheckedIndexedAccess": true,
|
|
21
|
+
"exactOptionalPropertyTypes": true,
|
|
22
|
+
"strict": true,
|
|
23
|
+
"jsx": "react-jsx",
|
|
24
|
+
"verbatimModuleSyntax": true,
|
|
25
|
+
"isolatedModules": true,
|
|
26
|
+
"noUncheckedSideEffectImports": true,
|
|
27
|
+
"moduleDetection": "force",
|
|
28
|
+
"skipLibCheck": true
|
|
29
|
+
},
|
|
30
|
+
"include": ["**/*.ts", "output.ts"],
|
|
31
|
+
"exclude": ["node_modules", "dist"]
|
|
32
|
+
}
|
package/tsup.config.ts
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { defineConfig } from 'tsup'
|
|
2
|
+
|
|
3
|
+
export default defineConfig({
|
|
4
|
+
entry: [
|
|
5
|
+
'external/**/*.ts',
|
|
6
|
+
'core/**/*.ts',
|
|
7
|
+
'box/**/*.ts',
|
|
8
|
+
'util/**/*.ts',
|
|
9
|
+
'adapters/**/*.ts'
|
|
10
|
+
],
|
|
11
|
+
format: ['cjs', 'esm'],
|
|
12
|
+
dts: true,
|
|
13
|
+
clean: true,
|
|
14
|
+
sourcemap: true,
|
|
15
|
+
minify: true,
|
|
16
|
+
outDir: 'dist',
|
|
17
|
+
})
|
package/types.d.ts
ADDED
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
// Types aligned with BS.config.json
|
|
2
|
+
|
|
3
|
+
export type Limit = number | "infinity";
|
|
4
|
+
|
|
5
|
+
export interface ProjectConfig {
|
|
6
|
+
workspace?: string;
|
|
7
|
+
testDir?: string;
|
|
8
|
+
versionControl?: {
|
|
9
|
+
before?: boolean;
|
|
10
|
+
after?: boolean;
|
|
11
|
+
autoPush?: boolean;
|
|
12
|
+
generateNotes?: boolean;
|
|
13
|
+
};
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export interface ModelPrimary {
|
|
17
|
+
provider: string;
|
|
18
|
+
name: string;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export interface ModelConfig {
|
|
22
|
+
primary?: ModelPrimary;
|
|
23
|
+
fallback?: unknown[];
|
|
24
|
+
endpoint?: string | null;
|
|
25
|
+
parameters?: Record<string, unknown>;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export interface SmartRotationConfig {
|
|
29
|
+
enabled?: boolean;
|
|
30
|
+
simple?: unknown[];
|
|
31
|
+
complex?: unknown[];
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export interface TimeoutConfig {
|
|
35
|
+
enabled?: boolean;
|
|
36
|
+
duration?: string;
|
|
37
|
+
notify?: boolean;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export interface LimitsConfig {
|
|
41
|
+
tokens?: number;
|
|
42
|
+
loops?: Limit;
|
|
43
|
+
timeout?: TimeoutConfig;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export interface SandboxConfig {
|
|
47
|
+
enabled?: boolean;
|
|
48
|
+
engine?: string;
|
|
49
|
+
memory?: string;
|
|
50
|
+
cpu?: number;
|
|
51
|
+
network?: string;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export type CommandRun = string | [string, string[]];
|
|
55
|
+
|
|
56
|
+
export interface CommandsConfig {
|
|
57
|
+
setup?: string | null;
|
|
58
|
+
run?: string | null;
|
|
59
|
+
test?: string | null;
|
|
60
|
+
timeoutMs?: number | null;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export interface InterfaceNotifications {
|
|
64
|
+
whatsapp?: boolean;
|
|
65
|
+
telegram?: boolean;
|
|
66
|
+
slack?: boolean;
|
|
67
|
+
email?: boolean;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
export interface InterfaceConfig {
|
|
71
|
+
channel?: string;
|
|
72
|
+
prompt?: string | null;
|
|
73
|
+
notifications?: InterfaceNotifications;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
export interface TeachConfig {
|
|
77
|
+
urls?: string[];
|
|
78
|
+
files?: string[];
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
export interface PathsConfig {
|
|
82
|
+
generatedMarkdown?: string;
|
|
83
|
+
artifactOutput?: string;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
export interface BoxSafeConfig {
|
|
87
|
+
project?: ProjectConfig;
|
|
88
|
+
model?: ModelConfig;
|
|
89
|
+
smartRotation?: SmartRotationConfig;
|
|
90
|
+
limits?: LimitsConfig;
|
|
91
|
+
sandbox?: SandboxConfig;
|
|
92
|
+
commands?: CommandsConfig;
|
|
93
|
+
interface?: InterfaceConfig;
|
|
94
|
+
paths?: PathsConfig;
|
|
95
|
+
teach?: TeachConfig;
|
|
96
|
+
}
|
package/util/ANSI.ts
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
// Basic ANSI escape codes for terminal colors
|
|
2
|
+
export enum ANSI {
|
|
3
|
+
Reset = "\x1b[0m",
|
|
4
|
+
Bold = "\x1b[1m",
|
|
5
|
+
Dim = "\x1b[2m",
|
|
6
|
+
|
|
7
|
+
// Foreground colors
|
|
8
|
+
Black = "\x1b[30m",
|
|
9
|
+
Red = "\x1b[31m",
|
|
10
|
+
Green = "\x1b[32m",
|
|
11
|
+
Yellow = "\x1b[33m",
|
|
12
|
+
Blue = "\x1b[34m",
|
|
13
|
+
Magenta = "\x1b[35m",
|
|
14
|
+
Cyan = "\x1b[36m",
|
|
15
|
+
White = "\x1b[37m",
|
|
16
|
+
|
|
17
|
+
// Bright foreground colors
|
|
18
|
+
Gray = "\x1b[90m",
|
|
19
|
+
RedBright = "\x1b[91m",
|
|
20
|
+
GreenBright = "\x1b[92m",
|
|
21
|
+
YellowBright = "\x1b[93m",
|
|
22
|
+
BlueBright = "\x1b[94m",
|
|
23
|
+
MagentaBright = "\x1b[95m",
|
|
24
|
+
CyanBright = "\x1b[96m",
|
|
25
|
+
WhiteBright = "\x1b[97m",
|
|
26
|
+
|
|
27
|
+
// Background colors
|
|
28
|
+
BgRed = "\x1b[41m",
|
|
29
|
+
}
|