smithers-orchestrator 0.1.16 → 0.1.17
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/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "smithers-orchestrator",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.17",
|
|
4
4
|
"description": "Build AI agents with Solid.js - Declarative JSX for Claude orchestration",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./src/index.ts",
|
|
@@ -51,7 +51,6 @@
|
|
|
51
51
|
"@anthropic-ai/sdk": "^0.71.2",
|
|
52
52
|
"@babel/core": "^7.28.6",
|
|
53
53
|
"@babel/preset-typescript": "^7.28.5",
|
|
54
|
-
"@dschz/bun-plugin-solid": "^1.0.4",
|
|
55
54
|
"@electric-sql/pglite": "^0.3.15",
|
|
56
55
|
"babel-preset-solid": "^1.9.10",
|
|
57
56
|
"commander": "^12.0.0",
|
package/preload.ts
CHANGED
|
@@ -1,10 +1,69 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
1
|
+
/**
|
|
2
|
+
* Preload script for Smithers orchestration files.
|
|
3
|
+
*
|
|
4
|
+
* This sets up the Solid JSX transform using babel directly,
|
|
5
|
+
* pointing to smithers-orchestrator/solid as the render module.
|
|
6
|
+
*/
|
|
7
|
+
import tsPreset from "@babel/preset-typescript";
|
|
8
|
+
import solidPreset from "babel-preset-solid";
|
|
9
|
+
|
|
10
|
+
const logPrefix = "\x1b[36m[smithers-solid-jsx]\x1b[0m";
|
|
11
|
+
|
|
12
|
+
await Bun.plugin({
|
|
13
|
+
name: "smithers-solid-jsx",
|
|
14
|
+
setup: (build) => {
|
|
15
|
+
let babel: typeof import("@babel/core") | null = null;
|
|
16
|
+
let babelTransformPresets: any[] | null = null;
|
|
17
|
+
|
|
18
|
+
// Only match tsx/jsx files outside of node_modules
|
|
19
|
+
// Use negative lookahead to exclude node_modules paths
|
|
20
|
+
build.onLoad({ filter: /^(?!.*node_modules).*\.[tj]sx$/ }, async ({ path }) => {
|
|
21
|
+
|
|
22
|
+
if (!babel) {
|
|
23
|
+
babel = await import("@babel/core");
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
if (!babelTransformPresets) {
|
|
27
|
+
babelTransformPresets = [
|
|
28
|
+
[tsPreset, {}],
|
|
29
|
+
[solidPreset, {
|
|
30
|
+
// Use universal mode (non-DOM) for the custom renderer
|
|
31
|
+
generate: "universal",
|
|
32
|
+
// Point to smithers-orchestrator's solid module for JSX runtime
|
|
33
|
+
moduleName: "smithers-orchestrator/solid",
|
|
34
|
+
// No hydration needed for non-DOM rendering
|
|
35
|
+
hydratable: false,
|
|
36
|
+
}],
|
|
37
|
+
];
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
console.log(`${logPrefix} Transforming: ${path}`);
|
|
41
|
+
const start = performance.now();
|
|
42
|
+
|
|
43
|
+
try {
|
|
44
|
+
const result = await babel.transformFileAsync(path, {
|
|
45
|
+
presets: babelTransformPresets,
|
|
46
|
+
filename: path,
|
|
47
|
+
sourceMaps: "inline",
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
const end = performance.now();
|
|
51
|
+
console.log(`${logPrefix} Transformed: ${path} in ${Math.round(end - start)}ms`);
|
|
52
|
+
|
|
53
|
+
if (!result || !result.code) {
|
|
54
|
+
console.warn(`${logPrefix} No code for: ${path}`);
|
|
55
|
+
// Return undefined to let bun handle the file normally
|
|
56
|
+
return undefined;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
return {
|
|
60
|
+
loader: "js",
|
|
61
|
+
contents: result.code,
|
|
62
|
+
};
|
|
63
|
+
} catch (error) {
|
|
64
|
+
console.error(`${logPrefix} Error transforming ${path}:`, error);
|
|
65
|
+
throw error;
|
|
66
|
+
}
|
|
67
|
+
});
|
|
68
|
+
},
|
|
69
|
+
});
|
|
@@ -1,11 +1,30 @@
|
|
|
1
1
|
import { spawn } from 'child_process'
|
|
2
2
|
import * as fs from 'fs'
|
|
3
3
|
import * as path from 'path'
|
|
4
|
+
import { fileURLToPath } from 'url'
|
|
4
5
|
import { OutputParser } from '../monitor/output-parser.jsx'
|
|
5
6
|
import { StreamFormatter } from '../monitor/stream-formatter.jsx'
|
|
6
7
|
import { LogWriter } from '../monitor/log-writer.jsx'
|
|
7
8
|
import { summarizeWithHaiku } from '../monitor/haiku-summarizer.jsx'
|
|
8
9
|
|
|
10
|
+
/**
|
|
11
|
+
* Find the preload.ts file from the smithers-orchestrator package
|
|
12
|
+
*/
|
|
13
|
+
function findPreloadPath(): string {
|
|
14
|
+
const __filename = fileURLToPath(import.meta.url)
|
|
15
|
+
const __dirname = path.dirname(__filename)
|
|
16
|
+
// Navigate up from src/orchestrator/commands to package root
|
|
17
|
+
let dir = __dirname
|
|
18
|
+
while (dir !== path.dirname(dir)) {
|
|
19
|
+
const preloadPath = path.join(dir, 'preload.ts')
|
|
20
|
+
if (fs.existsSync(preloadPath)) {
|
|
21
|
+
return preloadPath
|
|
22
|
+
}
|
|
23
|
+
dir = path.dirname(dir)
|
|
24
|
+
}
|
|
25
|
+
throw new Error('Could not find preload.ts - smithers-orchestrator may be incorrectly installed')
|
|
26
|
+
}
|
|
27
|
+
|
|
9
28
|
interface MonitorOptions {
|
|
10
29
|
file?: string
|
|
11
30
|
summary?: boolean
|
|
@@ -42,7 +61,8 @@ export async function monitor(fileArg?: string, options: MonitorOptions = {}) {
|
|
|
42
61
|
|
|
43
62
|
// Start execution
|
|
44
63
|
const startTime = Date.now()
|
|
45
|
-
const
|
|
64
|
+
const preloadPath = findPreloadPath()
|
|
65
|
+
const child = spawn('bun', ['--preload', preloadPath, '--install=fallback', filePath], {
|
|
46
66
|
stdio: ['inherit', 'pipe', 'pipe'],
|
|
47
67
|
shell: true,
|
|
48
68
|
})
|
|
@@ -1,6 +1,25 @@
|
|
|
1
1
|
import { spawn } from 'child_process'
|
|
2
2
|
import * as fs from 'fs'
|
|
3
3
|
import * as path from 'path'
|
|
4
|
+
import { fileURLToPath } from 'url'
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Find the preload.ts file from the smithers-orchestrator package
|
|
8
|
+
*/
|
|
9
|
+
function findPreloadPath(): string {
|
|
10
|
+
const __filename = fileURLToPath(import.meta.url)
|
|
11
|
+
const __dirname = path.dirname(__filename)
|
|
12
|
+
// Navigate up from src/orchestrator/commands to package root
|
|
13
|
+
let dir = __dirname
|
|
14
|
+
while (dir !== path.dirname(dir)) {
|
|
15
|
+
const preloadPath = path.join(dir, 'preload.ts')
|
|
16
|
+
if (fs.existsSync(preloadPath)) {
|
|
17
|
+
return preloadPath
|
|
18
|
+
}
|
|
19
|
+
dir = path.dirname(dir)
|
|
20
|
+
}
|
|
21
|
+
throw new Error('Could not find preload.ts - smithers-orchestrator may be incorrectly installed')
|
|
22
|
+
}
|
|
4
23
|
|
|
5
24
|
interface RunOptions {
|
|
6
25
|
file?: string
|
|
@@ -35,7 +54,8 @@ export async function run(fileArg?: string, options: RunOptions = {}) {
|
|
|
35
54
|
console.log('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━')
|
|
36
55
|
console.log('')
|
|
37
56
|
|
|
38
|
-
const
|
|
57
|
+
const preloadPath = findPreloadPath()
|
|
58
|
+
const child = spawn('bun', ['--preload', preloadPath, '--install=fallback', filePath], {
|
|
39
59
|
stdio: 'inherit',
|
|
40
60
|
shell: true,
|
|
41
61
|
})
|
|
@@ -172,9 +172,14 @@ export function SmithersProvider(props: SmithersProviderProps): JSX.Element {
|
|
|
172
172
|
isRebaseRequested: rebaseRequested,
|
|
173
173
|
}
|
|
174
174
|
|
|
175
|
+
// Use a function to render children lazily after context is established
|
|
176
|
+
// This is important for Solid.js universal renderer where children may
|
|
177
|
+
// be evaluated eagerly before the context provider is set up
|
|
178
|
+
const renderChildren = () => props.children
|
|
179
|
+
|
|
175
180
|
return (
|
|
176
181
|
<SmithersContext.Provider value={value}>
|
|
177
|
-
{
|
|
182
|
+
{renderChildren()}
|
|
178
183
|
</SmithersContext.Provider>
|
|
179
184
|
)
|
|
180
185
|
}
|
|
@@ -0,0 +1,227 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Unit tests for ReportTool.ts
|
|
3
|
+
*/
|
|
4
|
+
import { describe, test, expect, mock } from 'bun:test'
|
|
5
|
+
import { createReportTool, getReportToolDescription } from './ReportTool'
|
|
6
|
+
|
|
7
|
+
describe('getReportToolDescription', () => {
|
|
8
|
+
test('returns a non-empty string', () => {
|
|
9
|
+
const description = getReportToolDescription()
|
|
10
|
+
expect(typeof description).toBe('string')
|
|
11
|
+
expect(description.length).toBeGreaterThan(0)
|
|
12
|
+
})
|
|
13
|
+
|
|
14
|
+
test('contains Report Tool header', () => {
|
|
15
|
+
const description = getReportToolDescription()
|
|
16
|
+
expect(description).toContain('## Report Tool')
|
|
17
|
+
})
|
|
18
|
+
|
|
19
|
+
test('describes report types', () => {
|
|
20
|
+
const description = getReportToolDescription()
|
|
21
|
+
expect(description).toContain('progress')
|
|
22
|
+
expect(description).toContain('findings')
|
|
23
|
+
expect(description).toContain('warnings')
|
|
24
|
+
expect(description).toContain('errors')
|
|
25
|
+
expect(description).toContain('metrics')
|
|
26
|
+
expect(description).toContain('decisions')
|
|
27
|
+
})
|
|
28
|
+
|
|
29
|
+
test('includes example usage', () => {
|
|
30
|
+
const description = getReportToolDescription()
|
|
31
|
+
expect(description).toContain('Example usage')
|
|
32
|
+
expect(description).toContain('"type": "finding"')
|
|
33
|
+
expect(description).toContain('"title"')
|
|
34
|
+
expect(description).toContain('"content"')
|
|
35
|
+
})
|
|
36
|
+
|
|
37
|
+
test('mentions severity levels', () => {
|
|
38
|
+
const description = getReportToolDescription()
|
|
39
|
+
expect(description).toContain('severity')
|
|
40
|
+
expect(description).toContain('critical')
|
|
41
|
+
})
|
|
42
|
+
})
|
|
43
|
+
|
|
44
|
+
describe('createReportTool', () => {
|
|
45
|
+
// Create a mock context
|
|
46
|
+
const mockAddReport = mock(async () => 'report-123')
|
|
47
|
+
const mockContext = {
|
|
48
|
+
db: {
|
|
49
|
+
vcs: {
|
|
50
|
+
addReport: mockAddReport,
|
|
51
|
+
},
|
|
52
|
+
},
|
|
53
|
+
agentId: 'test-agent',
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
test('returns a tool with correct name', () => {
|
|
57
|
+
const tool = createReportTool(mockContext as any)
|
|
58
|
+
expect(tool.name).toBe('Report')
|
|
59
|
+
})
|
|
60
|
+
|
|
61
|
+
test('has description', () => {
|
|
62
|
+
const tool = createReportTool(mockContext as any)
|
|
63
|
+
expect(tool.description).toBeDefined()
|
|
64
|
+
expect(tool.description.length).toBeGreaterThan(0)
|
|
65
|
+
})
|
|
66
|
+
|
|
67
|
+
test('has input schema', () => {
|
|
68
|
+
const tool = createReportTool(mockContext as any)
|
|
69
|
+
expect(tool.inputSchema).toBeDefined()
|
|
70
|
+
expect(tool.inputSchema.type).toBe('object')
|
|
71
|
+
expect(tool.inputSchema.properties).toBeDefined()
|
|
72
|
+
})
|
|
73
|
+
|
|
74
|
+
test('input schema has required fields', () => {
|
|
75
|
+
const tool = createReportTool(mockContext as any)
|
|
76
|
+
expect(tool.inputSchema.required).toContain('type')
|
|
77
|
+
expect(tool.inputSchema.required).toContain('title')
|
|
78
|
+
expect(tool.inputSchema.required).toContain('content')
|
|
79
|
+
})
|
|
80
|
+
|
|
81
|
+
test('input schema has type enum', () => {
|
|
82
|
+
const tool = createReportTool(mockContext as any)
|
|
83
|
+
const typeProperty = tool.inputSchema.properties?.type as any
|
|
84
|
+
expect(typeProperty.enum).toContain('progress')
|
|
85
|
+
expect(typeProperty.enum).toContain('finding')
|
|
86
|
+
expect(typeProperty.enum).toContain('warning')
|
|
87
|
+
expect(typeProperty.enum).toContain('error')
|
|
88
|
+
expect(typeProperty.enum).toContain('metric')
|
|
89
|
+
expect(typeProperty.enum).toContain('decision')
|
|
90
|
+
})
|
|
91
|
+
|
|
92
|
+
test('input schema has severity enum', () => {
|
|
93
|
+
const tool = createReportTool(mockContext as any)
|
|
94
|
+
const severityProperty = tool.inputSchema.properties?.severity as any
|
|
95
|
+
expect(severityProperty.enum).toContain('info')
|
|
96
|
+
expect(severityProperty.enum).toContain('warning')
|
|
97
|
+
expect(severityProperty.enum).toContain('critical')
|
|
98
|
+
})
|
|
99
|
+
|
|
100
|
+
test('has execute function', () => {
|
|
101
|
+
const tool = createReportTool(mockContext as any)
|
|
102
|
+
expect(typeof tool.execute).toBe('function')
|
|
103
|
+
})
|
|
104
|
+
|
|
105
|
+
test('execute calls addReport with correct data', async () => {
|
|
106
|
+
const addReport = mock(async () => 'report-456')
|
|
107
|
+
const context = {
|
|
108
|
+
db: { vcs: { addReport } },
|
|
109
|
+
agentId: 'agent-123',
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
const tool = createReportTool(context as any)
|
|
113
|
+
await tool.execute({
|
|
114
|
+
type: 'progress',
|
|
115
|
+
title: 'Test Progress',
|
|
116
|
+
content: 'Progress update content',
|
|
117
|
+
})
|
|
118
|
+
|
|
119
|
+
expect(addReport).toHaveBeenCalledWith({
|
|
120
|
+
type: 'progress',
|
|
121
|
+
title: 'Test Progress',
|
|
122
|
+
content: 'Progress update content',
|
|
123
|
+
data: undefined,
|
|
124
|
+
severity: 'info',
|
|
125
|
+
agent_id: 'agent-123',
|
|
126
|
+
})
|
|
127
|
+
})
|
|
128
|
+
|
|
129
|
+
test('execute returns success with reportId', async () => {
|
|
130
|
+
const addReport = mock(async () => 'report-789')
|
|
131
|
+
const context = {
|
|
132
|
+
db: { vcs: { addReport } },
|
|
133
|
+
agentId: 'agent-123',
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
const tool = createReportTool(context as any)
|
|
137
|
+
const result = await tool.execute({
|
|
138
|
+
type: 'finding',
|
|
139
|
+
title: 'Test Finding',
|
|
140
|
+
content: 'Finding content',
|
|
141
|
+
})
|
|
142
|
+
|
|
143
|
+
expect(result.success).toBe(true)
|
|
144
|
+
expect(result.reportId).toBe('report-789')
|
|
145
|
+
expect(result.message).toContain('Test Finding')
|
|
146
|
+
})
|
|
147
|
+
|
|
148
|
+
test('defaults severity to critical for error type', async () => {
|
|
149
|
+
const addReport = mock(async () => 'report-err')
|
|
150
|
+
const context = {
|
|
151
|
+
db: { vcs: { addReport } },
|
|
152
|
+
agentId: 'agent-123',
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
const tool = createReportTool(context as any)
|
|
156
|
+
await tool.execute({
|
|
157
|
+
type: 'error',
|
|
158
|
+
title: 'Test Error',
|
|
159
|
+
content: 'Error content',
|
|
160
|
+
})
|
|
161
|
+
|
|
162
|
+
expect(addReport).toHaveBeenCalledWith(
|
|
163
|
+
expect.objectContaining({ severity: 'critical' })
|
|
164
|
+
)
|
|
165
|
+
})
|
|
166
|
+
|
|
167
|
+
test('defaults severity to warning for warning type', async () => {
|
|
168
|
+
const addReport = mock(async () => 'report-warn')
|
|
169
|
+
const context = {
|
|
170
|
+
db: { vcs: { addReport } },
|
|
171
|
+
agentId: 'agent-123',
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
const tool = createReportTool(context as any)
|
|
175
|
+
await tool.execute({
|
|
176
|
+
type: 'warning',
|
|
177
|
+
title: 'Test Warning',
|
|
178
|
+
content: 'Warning content',
|
|
179
|
+
})
|
|
180
|
+
|
|
181
|
+
expect(addReport).toHaveBeenCalledWith(
|
|
182
|
+
expect.objectContaining({ severity: 'warning' })
|
|
183
|
+
)
|
|
184
|
+
})
|
|
185
|
+
|
|
186
|
+
test('uses provided severity over default', async () => {
|
|
187
|
+
const addReport = mock(async () => 'report-custom')
|
|
188
|
+
const context = {
|
|
189
|
+
db: { vcs: { addReport } },
|
|
190
|
+
agentId: 'agent-123',
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
const tool = createReportTool(context as any)
|
|
194
|
+
await tool.execute({
|
|
195
|
+
type: 'error',
|
|
196
|
+
title: 'Test Error',
|
|
197
|
+
content: 'Error content',
|
|
198
|
+
severity: 'info', // Override critical default
|
|
199
|
+
})
|
|
200
|
+
|
|
201
|
+
expect(addReport).toHaveBeenCalledWith(
|
|
202
|
+
expect.objectContaining({ severity: 'info' })
|
|
203
|
+
)
|
|
204
|
+
})
|
|
205
|
+
|
|
206
|
+
test('includes data in report', async () => {
|
|
207
|
+
const addReport = mock(async () => 'report-data')
|
|
208
|
+
const context = {
|
|
209
|
+
db: { vcs: { addReport } },
|
|
210
|
+
agentId: 'agent-123',
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
const tool = createReportTool(context as any)
|
|
214
|
+
await tool.execute({
|
|
215
|
+
type: 'metric',
|
|
216
|
+
title: 'Test Metric',
|
|
217
|
+
content: 'Metric content',
|
|
218
|
+
data: { value: 42, unit: 'ms' },
|
|
219
|
+
})
|
|
220
|
+
|
|
221
|
+
expect(addReport).toHaveBeenCalledWith(
|
|
222
|
+
expect.objectContaining({
|
|
223
|
+
data: { value: 42, unit: 'ms' },
|
|
224
|
+
})
|
|
225
|
+
)
|
|
226
|
+
})
|
|
227
|
+
})
|