@studio-foundation/runner 0.3.0-beta.1 → 0.3.0-beta.5

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.
Files changed (78) hide show
  1. package/package.json +6 -3
  2. package/ARCHITECTURE.md +0 -53
  3. package/configs/agents/analyst.agent.yaml +0 -31
  4. package/configs/agents/code-generator.agent.yaml +0 -31
  5. package/configs/agents/generic.agent.yaml +0 -23
  6. package/src/__tests__/script-executor.test.ts +0 -180
  7. package/src/index.ts +0 -68
  8. package/src/integrations/integration-loader.test.ts +0 -88
  9. package/src/integrations/integration-loader.ts +0 -68
  10. package/src/middleware/anonymization.ts +0 -38
  11. package/src/plugins/index.ts +0 -4
  12. package/src/plugins/mcp-client.test.ts +0 -148
  13. package/src/plugins/mcp-client.ts +0 -128
  14. package/src/plugins/oauth-provider.test.ts +0 -167
  15. package/src/plugins/oauth-provider.ts +0 -175
  16. package/src/plugins/plugin-loader.test.ts +0 -114
  17. package/src/plugins/plugin-loader.ts +0 -90
  18. package/src/prompt-builder.test.ts +0 -167
  19. package/src/prompt-builder.ts +0 -332
  20. package/src/providers/anthropic.test.ts +0 -101
  21. package/src/providers/anthropic.ts +0 -135
  22. package/src/providers/mock.ts +0 -57
  23. package/src/providers/ollama.test.ts +0 -166
  24. package/src/providers/ollama.ts +0 -152
  25. package/src/providers/openai-responses.ts +0 -212
  26. package/src/providers/openai.test.ts +0 -67
  27. package/src/providers/openai.ts +0 -139
  28. package/src/providers/provider.ts +0 -54
  29. package/src/providers/registry.ts +0 -77
  30. package/src/runner.test.ts +0 -343
  31. package/src/runner.ts +0 -396
  32. package/src/script-executor.ts +0 -107
  33. package/src/tools/builtin/git.ts +0 -311
  34. package/src/tools/builtin/patch.ts +0 -257
  35. package/src/tools/builtin/repo-manager.ts +0 -142
  36. package/src/tools/builtin/search.ts +0 -108
  37. package/src/tools/builtin/shell.ts +0 -82
  38. package/src/tools/builtin/studio-run.ts +0 -73
  39. package/src/tools/builtin/web-search.test.ts +0 -122
  40. package/src/tools/builtin/web-search.ts +0 -101
  41. package/src/tools/errors.test.ts +0 -12
  42. package/src/tools/errors.ts +0 -6
  43. package/src/tools/plugin-loader.test.ts +0 -130
  44. package/src/tools/plugin-loader.ts +0 -203
  45. package/src/tools/skills/README.md +0 -49
  46. package/src/tools/skills/skill-loader.test.ts +0 -106
  47. package/src/tools/skills/skill-loader.ts +0 -62
  48. package/src/tools/tool-executor.test.ts +0 -88
  49. package/src/tools/tool-executor.ts +0 -84
  50. package/src/tools/tool-registry.ts +0 -130
  51. package/src/tools/yaml-executor.ts +0 -120
  52. package/src/utils/race-signal.test.ts +0 -50
  53. package/src/utils/race-signal.ts +0 -17
  54. package/templates/integrations/linear.integration.yaml +0 -35
  55. package/templates/integrations/slack.integration.yaml +0 -22
  56. package/templates/integrations/webhook.integration.yaml +0 -17
  57. package/templates/tools/git.tool.yaml +0 -80
  58. package/templates/tools/repo-manager.tool.yaml +0 -64
  59. package/templates/tools/search.tool.yaml +0 -22
  60. package/templates/tools/shell.tool.yaml +0 -19
  61. package/templates/tools/web-search.tool.yaml +0 -24
  62. package/tests/anonymization-middleware.test.ts +0 -61
  63. package/tests/anthropic.test.ts +0 -87
  64. package/tests/apply-patch.test.ts +0 -355
  65. package/tests/fixtures/tools/test-builtin.tool.yaml +0 -14
  66. package/tests/fixtures/tools/test-shell.tool.yaml +0 -19
  67. package/tests/mock-provider.test.ts +0 -104
  68. package/tests/openai.test.ts +0 -72
  69. package/tests/plugin-loader.test.ts +0 -54
  70. package/tests/prompt-builder.test.ts +0 -468
  71. package/tests/runner-anonymization.test.ts +0 -89
  72. package/tests/runner.test.ts +0 -885
  73. package/tests/studio-run.test.ts +0 -94
  74. package/tests/tool-executor.test.ts +0 -115
  75. package/tests/tool-registry.test.ts +0 -84
  76. package/tests/yaml-executor.test.ts +0 -76
  77. package/tsconfig.json +0 -20
  78. package/vitest.config.ts +0 -7
@@ -1,94 +0,0 @@
1
- import { describe, it, expect, vi } from 'vitest';
2
- import { createStudioRunTool } from '../src/tools/builtin/studio-run.js';
3
- import type { RunSpawner } from '@studio-foundation/contracts';
4
-
5
- function makeSpawner(overrides?: Partial<RunSpawner>): RunSpawner {
6
- return {
7
- spawnAndWait: vi.fn().mockResolvedValue({
8
- run_id: 'child-abc',
9
- status: 'success',
10
- output: { result: 'done' },
11
- }),
12
- ...overrides,
13
- };
14
- }
15
-
16
- describe('createStudioRunTool', () => {
17
- it('returns a tool named studio_run-run_pipeline', () => {
18
- const tools = createStudioRunTool({
19
- spawner: makeSpawner(),
20
- currentRunId: 'parent-1',
21
- currentDepth: 0,
22
- maxDepth: 3,
23
- });
24
- expect(tools).toHaveLength(1);
25
- expect(tools[0].name).toBe('studio_run-run_pipeline');
26
- });
27
-
28
- it('throws depth limit error before calling spawner', async () => {
29
- const spawner = makeSpawner();
30
- const tools = createStudioRunTool({
31
- spawner,
32
- currentRunId: 'parent-1',
33
- currentDepth: 3, // already at max
34
- maxDepth: 3,
35
- });
36
- const tool = tools[0];
37
- await expect(
38
- tool.execute({ pipeline: 'my-pipe', input: { x: 1 }, wait: true })
39
- ).rejects.toThrow('studio-run depth limit reached (max: 3)');
40
- expect(spawner.spawnAndWait).not.toHaveBeenCalled();
41
- });
42
-
43
- it('calls spawner with correct config and returns result', async () => {
44
- const spawner = makeSpawner();
45
- const tools = createStudioRunTool({
46
- spawner,
47
- currentRunId: 'parent-1',
48
- currentDepth: 0,
49
- maxDepth: 3,
50
- });
51
- const result = await tools[0].execute({
52
- pipeline: 'recipe-developer',
53
- input: { dish: 'pasta' },
54
- wait: true,
55
- });
56
- expect(spawner.spawnAndWait).toHaveBeenCalledWith({
57
- pipeline: 'recipe-developer',
58
- input: { dish: 'pasta' },
59
- parentRunId: 'parent-1',
60
- depth: 1,
61
- });
62
- expect(result).toEqual({
63
- success: true,
64
- output: { run_id: 'child-abc', status: 'success', output: { result: 'done' } },
65
- });
66
- });
67
-
68
- it('propagates error when spawner throws', async () => {
69
- const spawner = makeSpawner({
70
- spawnAndWait: vi.fn().mockRejectedValue(new Error('Child run child-abc failed: contract violation')),
71
- });
72
- const tools = createStudioRunTool({
73
- spawner,
74
- currentRunId: 'parent-1',
75
- currentDepth: 0,
76
- maxDepth: 3,
77
- });
78
- await expect(
79
- tools[0].execute({ pipeline: 'bad-pipe', input: {}, wait: true })
80
- ).rejects.toThrow('Child run child-abc failed');
81
- });
82
-
83
- it('throws when wait is false (not supported in v1)', async () => {
84
- const tools = createStudioRunTool({
85
- spawner: makeSpawner(),
86
- currentRunId: 'parent-1',
87
- currentDepth: 0,
88
- maxDepth: 3,
89
- });
90
- await expect(
91
- tools[0].execute({ pipeline: 'x', input: {}, wait: false })
92
- ).rejects.toThrow('wait: false is not supported');
93
- });
94
- });
@@ -1,115 +0,0 @@
1
- /**
2
- * Tool executor tests
3
- */
4
-
5
- import { describe, it, expect, beforeEach } from 'vitest';
6
- import { ToolExecutor } from '../src/tools/tool-executor.js';
7
- import { ToolRegistry, type Tool } from '../src/tools/tool-registry.js';
8
-
9
- describe('ToolExecutor', () => {
10
- let registry: ToolRegistry;
11
- let executor: ToolExecutor;
12
-
13
- beforeEach(() => {
14
- registry = new ToolRegistry();
15
- executor = new ToolExecutor(registry);
16
- });
17
-
18
- it('should execute a successful tool call', async () => {
19
- // Register a test tool
20
- const testTool: Tool = {
21
- name: 'test_tool',
22
- description: 'A test tool',
23
- parameters: {
24
- type: 'object',
25
- properties: { foo: { type: 'string' } },
26
- },
27
- execute: async (args) => ({
28
- success: true,
29
- output: { result: 'hello world', args }
30
- })
31
- };
32
- registry.register(testTool);
33
-
34
- // Execute the tool
35
- const result = await executor.execute({
36
- id: 'test-1',
37
- name: 'test_tool',
38
- arguments: { foo: 'bar' }
39
- });
40
-
41
- expect(result).toEqual({
42
- id: 'test-1',
43
- name: 'test_tool',
44
- arguments: { foo: 'bar' },
45
- result: { result: 'hello world', args: { foo: 'bar' } }
46
- });
47
- });
48
-
49
- it('should handle tool not found', async () => {
50
- const result = await executor.execute({
51
- id: 'test-1',
52
- name: 'unknown_tool',
53
- arguments: {}
54
- });
55
-
56
- expect(result).toEqual({
57
- id: 'test-1',
58
- name: 'unknown_tool',
59
- arguments: {},
60
- error: 'Tool not found: unknown_tool'
61
- });
62
- });
63
-
64
- it('should handle tool execution failure', async () => {
65
- const failingTool: Tool = {
66
- name: 'failing_tool',
67
- description: 'A tool that fails',
68
- parameters: {},
69
- execute: async () => ({
70
- success: false,
71
- output: null,
72
- error: 'Something went wrong'
73
- })
74
- };
75
- registry.register(failingTool);
76
-
77
- const result = await executor.execute({
78
- id: 'test-1',
79
- name: 'failing_tool',
80
- arguments: {}
81
- });
82
-
83
- expect(result).toEqual({
84
- id: 'test-1',
85
- name: 'failing_tool',
86
- arguments: {},
87
- error: 'Something went wrong'
88
- });
89
- });
90
-
91
- it('should handle tool that throws exception', async () => {
92
- const throwingTool: Tool = {
93
- name: 'throwing_tool',
94
- description: 'A tool that throws',
95
- parameters: {},
96
- execute: async () => {
97
- throw new Error('Unexpected error');
98
- }
99
- };
100
- registry.register(throwingTool);
101
-
102
- const result = await executor.execute({
103
- id: 'test-1',
104
- name: 'throwing_tool',
105
- arguments: {}
106
- });
107
-
108
- expect(result).toEqual({
109
- id: 'test-1',
110
- name: 'throwing_tool',
111
- arguments: {},
112
- error: 'Unexpected error'
113
- });
114
- });
115
- });
@@ -1,84 +0,0 @@
1
- import { describe, it, expect } from 'vitest';
2
- import { ToolRegistry } from '../src/tools/tool-registry.js';
3
-
4
- function makeTool(name: string) {
5
- return {
6
- name,
7
- description: `Tool ${name}`,
8
- parameters: {},
9
- execute: async () => ({ success: true as const, output: null }),
10
- };
11
- }
12
-
13
- describe('ToolRegistry.registerPlugin', () => {
14
- it('registers all tools in a plugin', () => {
15
- const registry = new ToolRegistry();
16
- registry.registerPlugin('my_plugin', [makeTool('my_plugin-foo'), makeTool('my_plugin-bar')]);
17
- expect(registry.has('my_plugin-foo')).toBe(true);
18
- expect(registry.has('my_plugin-bar')).toBe(true);
19
- });
20
-
21
- it('stores the prompt snippet for retrieval', () => {
22
- const registry = new ToolRegistry();
23
- registry.registerPlugin('my_plugin', [makeTool('my_plugin-foo')], 'Use foo carefully.');
24
- expect(registry.getActiveSnippets()).toEqual(['Use foo carefully.']);
25
- });
26
-
27
- it('getActiveSnippets returns empty when no snippets registered', () => {
28
- const registry = new ToolRegistry();
29
- registry.registerPlugin('my_plugin', [makeTool('my_plugin-foo')]);
30
- expect(registry.getActiveSnippets()).toEqual([]);
31
- });
32
- });
33
-
34
- describe('ToolRegistry.filter preserves snippet metadata', () => {
35
- it('filtered registry returns snippet for included tools', () => {
36
- const registry = new ToolRegistry();
37
- registry.registerPlugin('plug_a', [makeTool('plug_a-foo')], 'Snippet A');
38
- registry.registerPlugin('plug_b', [makeTool('plug_b-bar')], 'Snippet B');
39
-
40
- const filtered = registry.filter(['plug_a-foo']);
41
- expect(filtered.getActiveSnippets()).toEqual(['Snippet A']);
42
- });
43
-
44
- it('filtered registry does not return snippet for excluded tools', () => {
45
- const registry = new ToolRegistry();
46
- registry.registerPlugin('plug_a', [makeTool('plug_a-foo')], 'Snippet A');
47
- registry.registerPlugin('plug_b', [makeTool('plug_b-bar')], 'Snippet B');
48
-
49
- const filtered = registry.filter(['plug_a-foo']);
50
- expect(filtered.getActiveSnippets()).not.toContain('Snippet B');
51
- });
52
-
53
- it('normalizes dot-notation in filter with snippets', () => {
54
- const registry = new ToolRegistry();
55
- registry.registerPlugin('repo_manager', [makeTool('repo_manager-write_file')], 'Write files!');
56
- const filtered = registry.filter(['repo_manager.write_file']);
57
- expect(filtered.getActiveSnippets()).toEqual(['Write files!']);
58
- });
59
- });
60
-
61
- describe('clone', () => {
62
- it('creates an independent copy with all tools', () => {
63
- const registry = new ToolRegistry();
64
- registry.registerPlugin('my_plugin', [
65
- { name: 'my_plugin-cmd', description: 'test', parameters: {}, execute: async () => ({ content: 'ok' }) },
66
- ], 'my snippet');
67
-
68
- const clone = registry.clone();
69
- expect(clone.get('my_plugin-cmd')).toBeDefined();
70
- expect(clone.getActiveSnippets()).toContain('my snippet');
71
- });
72
-
73
- it('mutations to clone do not affect original', () => {
74
- const registry = new ToolRegistry();
75
- registry.registerPlugin('original', [
76
- { name: 'original-cmd', description: 'test', parameters: {}, execute: async () => ({ content: 'ok' }) },
77
- ]);
78
- const clone = registry.clone();
79
- clone.registerPlugin('extra', [
80
- { name: 'extra-cmd', description: 'test', parameters: {}, execute: async () => ({ content: 'ok' }) },
81
- ]);
82
- expect(registry.get('extra-cmd')).toBeUndefined();
83
- });
84
- });
@@ -1,76 +0,0 @@
1
- // runner/tests/yaml-executor.test.ts
2
- import { describe, it, expect } from 'vitest';
3
- import { renderTemplate, executeShellCommand } from '../src/tools/yaml-executor.js';
4
-
5
- describe('renderTemplate', () => {
6
- it('substitutes plain {{param}}', () => {
7
- expect(renderTemplate('echo {{message}}', { message: 'hello' })).toBe('echo hello');
8
- });
9
-
10
- it('renders {{#if param}}...{{/if}} when truthy', () => {
11
- expect(renderTemplate('git diff {{#if staged}}--cached{{/if}}', { staged: true }))
12
- .toBe('git diff --cached');
13
- });
14
-
15
- it('removes {{#if param}}...{{/if}} block when falsy', () => {
16
- expect(renderTemplate('git diff {{#if staged}}--cached{{/if}}', { staged: false }))
17
- .toBe('git diff ');
18
- });
19
-
20
- it('renders {{#if param}}...{{/if}} when param is absent', () => {
21
- expect(renderTemplate('git diff {{#if staged}}--cached{{/if}}', {}))
22
- .toBe('git diff ');
23
- });
24
-
25
- it('joins array with {{param | join sep}}', () => {
26
- expect(renderTemplate('git add {{files | join " "}}', { files: ['a.ts', 'b.ts'] }))
27
- .toBe('git add a.ts b.ts');
28
- });
29
-
30
- it('renders {{param | json}} as JSON string', () => {
31
- expect(renderTemplate('echo {{data | json}}', { data: ['a', 'b'] }))
32
- .toBe('echo ["a","b"]');
33
- });
34
-
35
- it('returns empty string for missing plain param', () => {
36
- expect(renderTemplate('{{missing}}', {})).toBe('');
37
- });
38
-
39
- it('handles multi-line templates', () => {
40
- const template = `{{#if create}}
41
- git checkout -b {{branch}}
42
- {{else}}
43
- git checkout {{branch}}
44
- {{/if}}`;
45
- // Note: we don't support {{else}} yet — it stays as literal text
46
- // Just ensure it doesn't crash and substitutes {{branch}}
47
- const result = renderTemplate(template, { create: false, branch: 'main' });
48
- expect(result).toContain('main');
49
- });
50
- });
51
-
52
- describe('executeShellCommand', () => {
53
- it('executes a command and returns stdout as text', async () => {
54
- const result = await executeShellCommand('echo hello', 'text', '/tmp');
55
- expect(result.success).toBe(true);
56
- expect(result.output).toBe('hello');
57
- });
58
-
59
- it('parses JSON output when parse_output is json', async () => {
60
- const result = await executeShellCommand('echo \'{"x":1}\'', 'json', '/tmp');
61
- expect(result.success).toBe(true);
62
- expect(result.output).toEqual({ x: 1 });
63
- });
64
-
65
- it('returns error on non-zero exit code', async () => {
66
- const result = await executeShellCommand('exit 1', 'text', '/tmp');
67
- expect(result.success).toBe(false);
68
- expect(result.error).toBeDefined();
69
- });
70
-
71
- it('returns error when JSON parsing fails', async () => {
72
- const result = await executeShellCommand('echo not-json', 'json', '/tmp');
73
- expect(result.success).toBe(false);
74
- expect(result.error).toMatch(/Failed to parse JSON/);
75
- });
76
- });
package/tsconfig.json DELETED
@@ -1,20 +0,0 @@
1
- {
2
- "compilerOptions": {
3
- "target": "ES2022",
4
- "module": "commonjs",
5
- "lib": ["ES2022"],
6
- "outDir": "./dist",
7
- "rootDir": "./src",
8
- "declaration": true,
9
- "declarationMap": true,
10
- "sourceMap": true,
11
- "strict": true,
12
- "esModuleInterop": true,
13
- "skipLibCheck": true,
14
- "forceConsistentCasingInFileNames": true,
15
- "resolveJsonModule": true,
16
- "moduleResolution": "node"
17
- },
18
- "include": ["src/**/*"],
19
- "exclude": ["node_modules", "dist", "tests"]
20
- }
package/vitest.config.ts DELETED
@@ -1,7 +0,0 @@
1
- import { defineConfig } from 'vitest/config';
2
-
3
- export default defineConfig({
4
- test: {
5
- exclude: ['dist/**', 'node_modules/**'],
6
- },
7
- });