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

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
package/package.json CHANGED
@@ -1,9 +1,12 @@
1
1
  {
2
2
  "name": "@studio-foundation/runner",
3
- "version": "0.3.0-beta.1",
3
+ "version": "0.3.0-beta.6",
4
4
  "description": "Multi-provider LLM agent runner with tool execution",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
7
+ "files": [
8
+ "dist"
9
+ ],
7
10
  "keywords": [
8
11
  "llm",
9
12
  "agent",
@@ -18,8 +21,8 @@
18
21
  "@modelcontextprotocol/sdk": "^1.26.0",
19
22
  "js-yaml": "^4.1.0",
20
23
  "openai": "^4.77.0",
21
- "@studio-foundation/anonymizer": "0.3.0-beta.1",
22
- "@studio-foundation/contracts": "0.3.0-beta.1"
24
+ "@studio-foundation/anonymizer": "0.3.0-beta.6",
25
+ "@studio-foundation/contracts": "0.3.0-beta.6"
23
26
  },
24
27
  "devDependencies": {
25
28
  "@types/js-yaml": "^4.0.9",
package/ARCHITECTURE.md DELETED
@@ -1,53 +0,0 @@
1
- # @studio-foundation/runner
2
-
3
- Agent runner multi-provider. Parle aux LLMs, exécute les tools, streame les tokens.
4
-
5
- ## Concept
6
-
7
- `runAgent()` prend un `AgentConfig` + contexte, appelle le LLM, exécute les tool calls en boucle, retourne un `AgentRunResult` complet avec tous les tool calls trackés et l'usage de tokens.
8
-
9
- ## Règles
10
-
11
- - Multi-provider : Anthropic, OpenAI, OpenAI Responses API, Mock — même interface `runAgent()`
12
- - CHAQUE tool call réel est tracké dans `AgentRunResult.tool_calls`
13
- - Le runner ne valide PAS — c'est le job de ralph
14
- - Le runner ne retry PAS — c'est le job de ralph
15
- - Les prompts sont assemblés ici (`prompt-builder.ts`) — le engine ne touche jamais aux prompts
16
- - Les tools sont enregistrés ici (`ToolRegistry`) — le engine ne sait pas ce qu'est `repo_manager-write_file`
17
- - Dépend UNIQUEMENT de `@studio-foundation/contracts`
18
-
19
- ## Fichiers clés
20
-
21
- - `runner.ts` — `runAgent()` fonction principale, deux chemins : agent loop provider et chat completions standard
22
- - `prompt-builder.ts` — assemblage system prompt + skills + context + task
23
- - `providers/anthropic.ts` — Claude (prompt caching, 90% cost reduction sur retries)
24
- - `providers/openai.ts` — OpenAI chat completions
25
- - `providers/openai-responses.ts` — OpenAI Responses API (provider owns the loop)
26
- - `providers/mock.ts` — mock provider pour tests sans API keys
27
- - `providers/registry.ts` — `ProviderRegistry`, `createDefaultRegistry()`
28
- - `tools/tool-registry.ts` — `ToolRegistry` (register, lookup)
29
- - `tools/tool-executor.ts` — `ToolExecutor` (dispatch tool calls)
30
- - `tools/plugin-loader.ts` — charge les `.tool.yaml` plugins
31
- - `tools/builtin/repo-manager.ts` — `repo_manager-read_file`, `write_file`, `list_files`
32
- - `tools/builtin/shell.ts` — `shell-run_command`
33
- - `tools/builtin/search.ts` — `search-search_codebase`
34
- - `tools/builtin/git.ts` — `git-checkout`, `git-commit`, `git-push`, `git-pull`, `git-status`, `git-diff`
35
- - `tools/builtin/patch.ts` — `patch-apply_patch`
36
- - `tools/builtin/studio-run.ts` — `studio_run` (spawn sous-pipeline, requiert `RunSpawner`)
37
- - `tools/skills/skill-loader.ts` — charge les `.skill.md` files
38
- - `middleware/anonymization.ts` — `AnonymizationMiddleware` (wraps `@studio-foundation/anonymizer`)
39
- - `plugins/plugin-loader.ts` — charge les plugins Claude Code (`.mcp.json` + `skills/`)
40
- - `plugins/mcp-client.ts` — `MCPClient` (connexion aux serveurs MCP)
41
- - `integrations/integration-loader.ts` — charge les `.integration.yaml`
42
-
43
- ## Hooks callbacks
44
-
45
- `RunnerCallbacks` (dans `@studio-foundation/contracts`) wire les hooks du engine vers le runner :
46
-
47
- - `onPreToolUse` — peut bloquer un tool call (`{ blocked: true }`)
48
- - `onPostToolUse` — peut injecter du feedback (`{ append_message: '...' }`)
49
- - `onToolCallStart/Complete`, `onAgentThinking/Progress/Token` — observabilité
50
-
51
- ## Anti-pattern : LE THÉÂTRE
52
-
53
- Le problème fondamental : les agents génèrent du JSON décrivant des actions au lieu de FAIRE les actions (`tool_calls: 0`). Le runner DOIT tracker tous les tool calls réels. La validation du théâtre est dans ralph, mais le runner fournit les données.
@@ -1,31 +0,0 @@
1
- name: analyst
2
- version: 1.0.0
3
- description: Agent specialized in code analysis and understanding
4
-
5
- systemMessage: |
6
- You are an expert code analyst that helps understand complex codebases.
7
-
8
- When analyzing code:
9
- - Identify key patterns and architectural decisions
10
- - Highlight potential issues or improvements
11
- - Explain complex logic clearly
12
- - Trace data flow and dependencies
13
- - Provide actionable insights
14
-
15
- Use search and file reading tools to explore the codebase thoroughly.
16
-
17
- capabilities:
18
- - code_analysis
19
- - architecture_review
20
- - dependency_tracing
21
- - pattern_recognition
22
- - documentation
23
-
24
- tools:
25
- - repo_manager
26
- - search
27
- - shell
28
-
29
- parameters:
30
- temperature: 0.5
31
- maxTokens: 4096
@@ -1,31 +0,0 @@
1
- name: code-generator
2
- version: 1.0.0
3
- description: Agent specialized in generating and modifying code
4
-
5
- systemMessage: |
6
- You are an expert software engineer that writes clean, efficient, and well-documented code.
7
-
8
- When generating code:
9
- - Follow best practices and conventions for the target language
10
- - Include appropriate error handling
11
- - Add clear comments and documentation
12
- - Use modern language features appropriately
13
- - Ensure code is production-ready
14
-
15
- Use the available tools to read existing code, write new files, and search the codebase.
16
-
17
- capabilities:
18
- - code_generation
19
- - code_modification
20
- - refactoring
21
- - file_operations
22
- - code_analysis
23
-
24
- tools:
25
- - repo_manager
26
- - search
27
- - shell
28
-
29
- parameters:
30
- temperature: 0.3
31
- maxTokens: 4096
@@ -1,23 +0,0 @@
1
- name: generic
2
- version: 1.0.0
3
- description: General-purpose assistant agent
4
-
5
- systemMessage: |
6
- You are a helpful assistant that can perform tasks using available tools.
7
- Be concise and accurate in your responses.
8
- Use tools when necessary to accomplish the user's goals.
9
-
10
- capabilities:
11
- - general_qa
12
- - file_operations
13
- - command_execution
14
- - code_search
15
-
16
- tools:
17
- - repo_manager
18
- - shell
19
- - search
20
-
21
- parameters:
22
- temperature: 0.7
23
- maxTokens: 2048
@@ -1,180 +0,0 @@
1
- import { describe, it, expect, vi, beforeEach } from 'vitest';
2
- import * as cp from 'node:child_process';
3
- import * as fs from 'node:fs';
4
- import { EventEmitter } from 'node:events';
5
-
6
- // Mock child_process and fs before importing the module under test
7
- vi.mock('node:child_process');
8
- vi.mock('node:fs');
9
-
10
- import { runScript } from '../script-executor.js';
11
- import type { AgentContext } from '../prompt-builder.js';
12
-
13
- function makeContext(overrides?: Partial<AgentContext>): AgentContext {
14
- return {
15
- input: 'test input',
16
- ...overrides,
17
- } as AgentContext;
18
- }
19
-
20
- function makeSpawnMock(opts: {
21
- stdout?: string;
22
- stderr?: string;
23
- exitCode?: number;
24
- errorEvent?: Error;
25
- }) {
26
- const proc = new EventEmitter() as ReturnType<typeof cp.spawn>;
27
- (proc as any).stdout = new EventEmitter();
28
- (proc as any).stderr = new EventEmitter();
29
- (proc as any).stdin = { write: vi.fn(), end: vi.fn() };
30
- (proc as any).kill = vi.fn();
31
-
32
- vi.mocked(cp.spawn).mockReturnValue(proc as any);
33
-
34
- // Simulate async process lifecycle
35
- setTimeout(() => {
36
- if (opts.errorEvent) {
37
- proc.emit('error', opts.errorEvent);
38
- return;
39
- }
40
- if (opts.stdout) (proc as any).stdout.emit('data', Buffer.from(opts.stdout));
41
- if (opts.stderr) (proc as any).stderr.emit('data', Buffer.from(opts.stderr));
42
- proc.emit('close', opts.exitCode ?? 0);
43
- }, 10);
44
-
45
- return proc;
46
- }
47
-
48
- beforeEach(() => {
49
- vi.mocked(fs.existsSync).mockReturnValue(false);
50
- vi.clearAllMocks();
51
- });
52
-
53
- describe('runScript', () => {
54
- it('parses stdout JSON and returns output on exit 0', async () => {
55
- const output = { result: 'ok', count: 42 };
56
- makeSpawnMock({ stdout: JSON.stringify(output), exitCode: 0 });
57
-
58
- const result = await runScript({
59
- scriptPath: 'scripts/parse.py',
60
- runtime: 'python',
61
- context: makeContext(),
62
- });
63
-
64
- expect(result.output).toEqual(output);
65
- expect(result.tool_calls).toEqual([]);
66
- expect(result.tool_calls_count).toBe(0);
67
- expect(result.error).toBeUndefined();
68
- expect(result.duration_ms).toBeGreaterThanOrEqual(0);
69
- });
70
-
71
- it('sets error when script exits with non-zero code', async () => {
72
- makeSpawnMock({ stdout: '', stderr: 'FileNotFoundError', exitCode: 1 });
73
-
74
- const result = await runScript({
75
- scriptPath: 'scripts/parse.py',
76
- runtime: 'python',
77
- context: makeContext(),
78
- });
79
-
80
- expect(result.output).toBeNull();
81
- expect(result.error).toMatch(/exited with code 1/);
82
- expect(result.error).toMatch(/FileNotFoundError/);
83
- });
84
-
85
- it('sets error when stdout is not valid JSON', async () => {
86
- makeSpawnMock({ stdout: 'not json at all', exitCode: 0 });
87
-
88
- const result = await runScript({
89
- scriptPath: 'scripts/parse.py',
90
- runtime: 'python',
91
- context: makeContext(),
92
- });
93
-
94
- expect(result.output).toBeNull();
95
- expect(result.error).toMatch(/not valid JSON/);
96
- expect(result.error).toMatch(/not json at all/);
97
- });
98
-
99
- it('sets error on process spawn error', async () => {
100
- const proc = new EventEmitter() as ReturnType<typeof cp.spawn>;
101
- (proc as any).stdout = new EventEmitter();
102
- (proc as any).stderr = new EventEmitter();
103
- (proc as any).stdin = { write: vi.fn(), end: vi.fn() };
104
- vi.mocked(cp.spawn).mockReturnValue(proc as any);
105
- setTimeout(() => proc.emit('error', new Error('ENOENT: python3 not found')), 10);
106
-
107
- const result = await runScript({
108
- scriptPath: 'scripts/parse.py',
109
- runtime: 'python',
110
- context: makeContext(),
111
- });
112
-
113
- expect(result.error).toMatch(/process error/);
114
- expect(result.error).toMatch(/ENOENT/);
115
- });
116
-
117
- it('uses python3 command for python runtime', async () => {
118
- makeSpawnMock({ stdout: '{}', exitCode: 0 });
119
-
120
- await runScript({ scriptPath: 'scripts/parse.py', runtime: 'python', context: makeContext() });
121
-
122
- expect(vi.mocked(cp.spawn)).toHaveBeenCalledWith(
123
- 'python3',
124
- ['scripts/parse.py'],
125
- expect.objectContaining({ stdio: ['pipe', 'pipe', 'pipe'] }),
126
- );
127
- });
128
-
129
- it('uses node command for node runtime', async () => {
130
- makeSpawnMock({ stdout: '{}', exitCode: 0 });
131
-
132
- await runScript({ scriptPath: 'scripts/parse.js', runtime: 'node', context: makeContext() });
133
-
134
- expect(vi.mocked(cp.spawn)).toHaveBeenCalledWith('node', ['scripts/parse.js'], expect.anything());
135
- });
136
-
137
- it('uses sh command for shell runtime', async () => {
138
- makeSpawnMock({ stdout: '{}', exitCode: 0 });
139
-
140
- await runScript({ scriptPath: 'scripts/run.sh', runtime: 'shell', context: makeContext() });
141
-
142
- expect(vi.mocked(cp.spawn)).toHaveBeenCalledWith('sh', ['scripts/run.sh'], expect.anything());
143
- });
144
-
145
- it('activates venv when venv/ directory exists', async () => {
146
- vi.mocked(fs.existsSync).mockImplementation((p) => String(p).endsWith('venv'));
147
- makeSpawnMock({ stdout: '{}', exitCode: 0 });
148
-
149
- await runScript({
150
- scriptPath: 'scripts/parse.py',
151
- runtime: 'python',
152
- context: makeContext(),
153
- cwd: '/project',
154
- });
155
-
156
- const spawnCall = vi.mocked(cp.spawn).mock.calls[0];
157
- const spawnEnv = (spawnCall[2] as any).env;
158
- expect(spawnEnv.VIRTUAL_ENV).toBe('/project/venv');
159
- expect(spawnEnv.PATH).toContain('/project/venv/bin');
160
- });
161
-
162
- it('writes context JSON to stdin', async () => {
163
- const stdinMock = { write: vi.fn(), end: vi.fn() };
164
- const proc = new EventEmitter() as ReturnType<typeof cp.spawn>;
165
- (proc as any).stdout = new EventEmitter();
166
- (proc as any).stderr = new EventEmitter();
167
- (proc as any).stdin = stdinMock;
168
- vi.mocked(cp.spawn).mockReturnValue(proc as any);
169
- setTimeout(() => {
170
- (proc as any).stdout.emit('data', Buffer.from('{"ok":true}'));
171
- proc.emit('close', 0);
172
- }, 10);
173
-
174
- const ctx = makeContext({ additional_context: 'hello world' });
175
- await runScript({ scriptPath: 'scripts/parse.py', runtime: 'python', context: ctx });
176
-
177
- expect(stdinMock.write).toHaveBeenCalledWith(JSON.stringify(ctx));
178
- expect(stdinMock.end).toHaveBeenCalled();
179
- });
180
- });
package/src/index.ts DELETED
@@ -1,68 +0,0 @@
1
- /**
2
- * @studio/runner - Multi-provider LLM agent runner with tool execution
3
- */
4
-
5
- // Main runner function
6
- export { runAgent } from './runner.js';
7
- export type { RunAgentConfig, AgentRunResult } from './runner.js';
8
-
9
- // Script executor
10
- export { runScript } from './script-executor.js';
11
- export type { ScriptExecutorConfig } from './script-executor.js';
12
-
13
- // Prompt builder
14
- export { buildPrompt } from './prompt-builder.js';
15
- export type {
16
- TaskInput,
17
- AgentContext,
18
- GroupFeedbackContext,
19
- ExecutionContext,
20
- PromptBuildConfig
21
- } from './prompt-builder.js';
22
-
23
- // Providers
24
- export type { Provider, AgentLoopProvider, AgentLoopResult } from './providers/provider.js';
25
- export { isAgentLoopProvider } from './providers/provider.js';
26
- export { OpenAIProvider } from './providers/openai.js';
27
- export { OpenAIResponsesProvider } from './providers/openai-responses.js';
28
- export { AnthropicProvider } from './providers/anthropic.js';
29
- export { OllamaProvider } from './providers/ollama.js';
30
- export { ProviderRegistry, createDefaultRegistry } from './providers/registry.js';
31
- export { MockProvider } from './providers/mock.js';
32
- export type { MockStageConfig } from './providers/mock.js';
33
-
34
- // Tools
35
- export { ToolRegistry } from './tools/tool-registry.js';
36
- export { ToolExecutor } from './tools/tool-executor.js';
37
- export type { Tool, ToolResult } from './tools/tool-registry.js';
38
-
39
- // Builtin tools
40
- export { createRepoManagerTools } from './tools/builtin/repo-manager.js';
41
- export { createShellTools } from './tools/builtin/shell.js';
42
- export { createSearchTools } from './tools/builtin/search.js';
43
- export { createPatchTools } from './tools/builtin/patch.js';
44
- export { createGitTools } from './tools/builtin/git.js';
45
- export { createStudioRunTool, STUDIO_RUN_PROMPT_SNIPPET } from './tools/builtin/studio-run.js';
46
- export { createWebSearchTools, WEB_SEARCH_PROMPT_SNIPPET } from './tools/builtin/web-search.js';
47
-
48
- export { loadProjectTools, listAvailableToolTemplates, getBundledToolTemplate, BUILTIN_TOOL_NAMES } from './tools/plugin-loader.js';
49
- export type { LoadedPlugin } from './tools/plugin-loader.js';
50
-
51
- // Anonymization middleware
52
- export { AnonymizationMiddleware } from './middleware/anonymization.js';
53
- export type { AnonymizerOptions } from '@studio-foundation/anonymizer';
54
-
55
- // Plugin system (Claude Code plugin compatibility)
56
- export { loadPlugins, MCPClient, StudioOAuthProvider } from './plugins/index.js';
57
- export type { PluginManifest, MCPServerDef, SkillContent } from './plugins/index.js';
58
-
59
- // Skill loader
60
- export { loadSkills, loadSkill, validateSkillManifest } from './tools/skills/skill-loader.js';
61
- export type { SkillManifest } from './tools/skills/skill-loader.js';
62
-
63
- // Integration plugin system
64
- export {
65
- getBundledIntegrationTemplate,
66
- listAvailableIntegrationTemplates,
67
- loadProjectIntegrations,
68
- } from './integrations/integration-loader.js';
@@ -1,88 +0,0 @@
1
- import { describe, it, expect, beforeAll, afterAll } from 'vitest';
2
- import { mkdtemp, mkdir, writeFile, rm } from 'node:fs/promises';
3
- import { join } from 'node:path';
4
- import { tmpdir } from 'node:os';
5
- import {
6
- getBundledIntegrationTemplate,
7
- listAvailableIntegrationTemplates,
8
- loadProjectIntegrations,
9
- } from './integration-loader.js';
10
-
11
- let tmpDir: string;
12
-
13
- beforeAll(async () => {
14
- tmpDir = await mkdtemp(join(tmpdir(), 'studio-integration-loader-test-'));
15
- });
16
-
17
- afterAll(async () => {
18
- await rm(tmpDir, { recursive: true, force: true });
19
- });
20
-
21
- describe('getBundledIntegrationTemplate', () => {
22
- it('returns YAML content for a known bundled integration', async () => {
23
- const content = await getBundledIntegrationTemplate('linear');
24
- expect(content).not.toBeNull();
25
- expect(content).toContain('name: linear');
26
- });
27
-
28
- it('returns null for unknown integration name', async () => {
29
- const content = await getBundledIntegrationTemplate('doesnotexist');
30
- expect(content).toBeNull();
31
- });
32
- });
33
-
34
- describe('listAvailableIntegrationTemplates', () => {
35
- it('returns at least linear, slack, webhook', async () => {
36
- const templates = await listAvailableIntegrationTemplates();
37
- const names = templates.map(t => t.name);
38
- expect(names).toContain('linear');
39
- expect(names).toContain('slack');
40
- expect(names).toContain('webhook');
41
- });
42
-
43
- it('each entry has name and description', async () => {
44
- const templates = await listAvailableIntegrationTemplates();
45
- for (const t of templates) {
46
- expect(typeof t.name).toBe('string');
47
- expect(typeof t.description).toBe('string');
48
- }
49
- });
50
- });
51
-
52
- describe('loadProjectIntegrations', () => {
53
- it('returns empty array when directory does not exist', async () => {
54
- const result = await loadProjectIntegrations('/nonexistent/path');
55
- expect(result).toEqual([]);
56
- });
57
-
58
- it('loads valid .integration.yaml files', async () => {
59
- const intDir = join(tmpDir, 'integrations');
60
- await mkdir(intDir, { recursive: true });
61
- await writeFile(join(intDir, 'test.integration.yaml'), `
62
- name: test
63
- version: 1
64
- description: "Test integration"
65
- config:
66
- required:
67
- - TEST_API_KEY
68
- test:
69
- type: http
70
- endpoint: https://api.test.com/health
71
- expect:
72
- status: 200
73
- `);
74
- const result = await loadProjectIntegrations(intDir);
75
- expect(result).toHaveLength(1);
76
- expect(result[0]!.name).toBe('test');
77
- expect(result[0]!.config?.required).toEqual(['TEST_API_KEY']);
78
- });
79
-
80
- it('ignores non-.integration.yaml files', async () => {
81
- const intDir = join(tmpDir, 'integrations-mixed');
82
- await mkdir(intDir, { recursive: true });
83
- await writeFile(join(intDir, 'readme.txt'), 'hello');
84
- await writeFile(join(intDir, 'other.yaml'), 'name: other\nversion: 1');
85
- const result = await loadProjectIntegrations(intDir);
86
- expect(result).toHaveLength(0);
87
- });
88
- });
@@ -1,68 +0,0 @@
1
- // runner/src/integrations/integration-loader.ts
2
- import { readdir, readFile } from 'node:fs/promises';
3
- import { existsSync } from 'node:fs';
4
- import { resolve } from 'node:path';
5
- import yaml from 'js-yaml';
6
- import type { IntegrationPluginDef } from '@studio-foundation/contracts';
7
-
8
- const BUNDLED_INTEGRATION_TEMPLATES_DIR = resolve(
9
- __dirname,
10
- '../../templates/integrations'
11
- );
12
-
13
- /**
14
- * Return the raw YAML content of a bundled integration template by name.
15
- * Returns null if the integration does not exist in the bundled registry.
16
- */
17
- export async function getBundledIntegrationTemplate(name: string): Promise<string | null> {
18
- const filePath = resolve(BUNDLED_INTEGRATION_TEMPLATES_DIR, `${name}.integration.yaml`);
19
- try {
20
- return await readFile(filePath, 'utf-8');
21
- } catch {
22
- return null;
23
- }
24
- }
25
-
26
- /**
27
- * List all integration plugins available for installation from the bundled registry.
28
- * Returns an array of { name, description } objects.
29
- */
30
- export async function listAvailableIntegrationTemplates(): Promise<{ name: string; description: string }[]> {
31
- let files: string[];
32
- try {
33
- files = (await readdir(BUNDLED_INTEGRATION_TEMPLATES_DIR))
34
- .filter(f => f.endsWith('.integration.yaml'))
35
- .sort();
36
- } catch {
37
- return [];
38
- }
39
- const result: { name: string; description: string }[] = [];
40
- for (const file of files) {
41
- const content = await readFile(resolve(BUNDLED_INTEGRATION_TEMPLATES_DIR, file), 'utf-8');
42
- const def = yaml.load(content) as IntegrationPluginDef;
43
- result.push({ name: file.replace('.integration.yaml', ''), description: def.description ?? '' });
44
- }
45
- return result;
46
- }
47
-
48
- /**
49
- * Load all `.integration.yaml` files from a project's integrations directory.
50
- * Returns an empty array if the directory does not exist.
51
- */
52
- export async function loadProjectIntegrations(integrationsDir: string): Promise<IntegrationPluginDef[]> {
53
- if (!existsSync(integrationsDir)) return [];
54
-
55
- let files: string[];
56
- try {
57
- files = (await readdir(integrationsDir)).filter(f => f.endsWith('.integration.yaml'));
58
- } catch {
59
- return [];
60
- }
61
-
62
- const result: IntegrationPluginDef[] = [];
63
- for (const file of files.sort()) {
64
- const content = await readFile(resolve(integrationsDir, file), 'utf-8');
65
- result.push(yaml.load(content) as IntegrationPluginDef);
66
- }
67
- return result;
68
- }
@@ -1,38 +0,0 @@
1
- import { anonymize, deanonymize } from '@studio-foundation/anonymizer';
2
- import type { AnonymizerOptions } from '@studio-foundation/anonymizer';
3
-
4
- /**
5
- * Stateful middleware that anonymizes text before sending to LLM
6
- * and deanonymizes text after receiving from LLM.
7
- * Accumulates the keymap across calls for cross-call consistency.
8
- */
9
- export class AnonymizationMiddleware {
10
- private keymap: Record<string, string> = {};
11
- private options?: Omit<AnonymizerOptions, 'seedKeymap'>;
12
-
13
- constructor(options?: Omit<AnonymizerOptions, 'seedKeymap'>) {
14
- this.options = options;
15
- }
16
-
17
- /**
18
- * Anonymize text. Accumulated keymap grows with each call.
19
- * Same PII value always gets the same token (guaranteed by seedKeymap).
20
- */
21
- anonymize(text: string): string {
22
- const result = anonymize(text, { ...this.options, seedKeymap: this.keymap });
23
- // result.keymap is the full accumulated keymap (seed + new entries)
24
- this.keymap = result.keymap;
25
- return result.text;
26
- }
27
-
28
- /**
29
- * Deanonymize text using accumulated keymap.
30
- */
31
- deanonymize(text: string): string {
32
- return deanonymize(text, this.keymap);
33
- }
34
-
35
- getKeymap(): Record<string, string> {
36
- return { ...this.keymap };
37
- }
38
- }
@@ -1,4 +0,0 @@
1
- export { loadPlugins } from './plugin-loader.js';
2
- export type { PluginManifest, MCPServerDef, SkillContent } from './plugin-loader.js';
3
- export { MCPClient } from './mcp-client.js';
4
- export { StudioOAuthProvider } from './oauth-provider.js';