From a1a47ab2a50bddfe75b28407bd08919ff63ffd3b Mon Sep 17 00:00:00 2001 From: Maksym Yezhov Date: Wed, 27 May 2026 22:06:42 -0700 Subject: [PATCH] feat: v2 - ai assistant - architect subagent --- .../agent-skills/componentYamlFormat/SKILL.md | 95 +++++++++++++++++++ .../agent-skills/tangleBestPractices/SKILL.md | 34 +++++++ .../agents/subagents/pipelineArchitect.ts | 52 ++++++++++ src/agent/agents/tangleDispatcher.ts | 11 ++- src/agent/config.ts | 10 ++ src/agent/middleware/observability.ts | 1 + src/agent/prompts/architect.md | 83 ++++++++++++++++ src/agent/prompts/dispatcher.md | 23 +++-- src/agent/session.ts | 4 + src/agent/skills/loader.ts | 47 +++++++++ src/agent/worker.ts | 3 + 11 files changed, 353 insertions(+), 10 deletions(-) create mode 100644 public/agent-skills/componentYamlFormat/SKILL.md create mode 100644 public/agent-skills/tangleBestPractices/SKILL.md create mode 100644 src/agent/agents/subagents/pipelineArchitect.ts create mode 100644 src/agent/prompts/architect.md create mode 100644 src/agent/skills/loader.ts diff --git a/public/agent-skills/componentYamlFormat/SKILL.md b/public/agent-skills/componentYamlFormat/SKILL.md new file mode 100644 index 000000000..16f70faca --- /dev/null +++ b/public/agent-skills/componentYamlFormat/SKILL.md @@ -0,0 +1,95 @@ +--- +name: Component YAML Format +description: Reference for the Tangle component YAML specification format +--- + +# Tangle Component YAML Format + +## Basic Structure + +```yaml +name: Component Name +description: What this component does +metadata: + annotations: + cloud_pipelines.net: "true" + +inputs: + - name: input_data + type: String + description: Description of input + - name: config + type: String + default: "default_value" + optional: true + +outputs: + - name: output_data + type: String + description: Description of output + +implementation: + container: + image: python:3.10 + command: + - sh + - -c + - | + python3 -c " + # inline Python code + " + args: + - --input + - { inputPath: input_data } + - --output + - { outputPath: output_data } + - --config + - { inputValue: config } +``` + +## Types + +Common input/output types: + +- `String` — text data or file paths +- `Integer` — whole numbers +- `Float` — decimal numbers +- `Boolean` — true/false +- `JsonObject` — structured JSON data +- `JsonArray` — JSON arrays +- `URI` — file URIs or URLs +- `ApacheParquet` — Parquet files (artifact type) +- `CSV` — CSV files (artifact type) + +## Graph Implementation (Pipelines) + +Pipelines use `implementation.graph` instead of `implementation.container`: + +```yaml +name: My Pipeline +implementation: + graph: + tasks: + task-name: + componentRef: + name: Component Name + spec: { ... } + arguments: + input_name: "{{inputs.pipeline_input}}" + other_input: + taskOutput: + taskId: other-task + outputName: output_name + outputValues: + pipeline_output: + taskOutput: + taskId: final-task + outputName: result +``` + +## Argument References + +- `{{inputs.name}}` — reference a pipeline-level input +- `{{tasks.taskName.outputs.outputName}}` — reference a task output +- `{graphInput: {inputName: name}}` — object form of input reference +- `{taskOutput: {taskId: name, outputName: name}}` — object form of task output reference diff --git a/public/agent-skills/tangleBestPractices/SKILL.md b/public/agent-skills/tangleBestPractices/SKILL.md new file mode 100644 index 000000000..6f88dd53d --- /dev/null +++ b/public/agent-skills/tangleBestPractices/SKILL.md @@ -0,0 +1,34 @@ +--- +name: Tangle Pipeline Best Practices +description: Guidelines for building well-structured, maintainable ML pipelines in Tangle +--- + +# Tangle Pipeline Best Practices + +## Pipeline Structure + +1. **Logical Stages**: Organize pipelines into clear stages — data ingestion, preprocessing, training, evaluation, deployment. +2. **Subgraph Grouping**: Group related tasks into subgraphs. Each subgraph should represent one logical stage. +3. **Size Limits**: Keep subgraphs under 7 nodes. If a subgraph grows larger, split it. +4. **Naming**: Use descriptive, human-readable names for tasks. "Preprocess Training Data" not "preprocess_1". + +## Component Selection + +1. **Search First**: Always check the component registry before creating new components. +2. **Single Responsibility**: Prefer components that do one thing well over monolithic ones. +3. **Type Compatibility**: Verify input/output types match when connecting components. +4. **Reuse**: If a component exists that's close to what you need, prefer it over creating a new one. + +## Data Flow + +1. **No Cycles**: Pipeline graphs must be directed acyclic graphs (DAGs). +2. **Explicit Connections**: Always use bindings to connect data flow — avoid implicit dependencies. +3. **Pipeline Inputs**: Use pipeline-level inputs for configurable parameters that should be settable at run time. +4. **Pipeline Outputs**: Define pipeline-level outputs for results that need to be accessible after the run. + +## Common Patterns + +- **Fan-out**: One source feeding multiple processors (e.g., train/test split). +- **Fan-in**: Multiple sources feeding into one aggregator. +- **Chain**: Linear sequence of transforms (most common). +- **Conditional**: Use task-level `isEnabled` for optional processing steps. diff --git a/src/agent/agents/subagents/pipelineArchitect.ts b/src/agent/agents/subagents/pipelineArchitect.ts new file mode 100644 index 000000000..58ca6fcf8 --- /dev/null +++ b/src/agent/agents/subagents/pipelineArchitect.ts @@ -0,0 +1,52 @@ +/** + * Pipeline-architect sub-agent — designs and builds new pipelines (or + * new stages within an existing pipeline) from a high-level user goal, + * applying edits through the CSOM tool surface inside an undo group. + * + * Skill content is awaited at agent-construction time via + * `session.skillsLoader.getSkill(id)`. The worker warms the loader at + * `init` (fire-and-forget) so by the time a turn starts the underlying + * promises are usually already resolved. + */ +import { Agent } from "@openai/agents"; + +import { requireOrchestratorModel } from "../../config"; +import { attachObservabilityHooks } from "../../middleware/observability"; +import architectPrompt from "../../prompts/architect.md?raw"; +import type { AgentSession } from "../../session"; +import { createCsomTools } from "../../tools/csomTools"; +import { createRunTools } from "../../tools/runTools"; + +const REFERENCE_SKILL_IDS = [ + "tangleBestPractices", + "componentYamlFormat", +] as const; + +async function buildInstructions(session: AgentSession): Promise { + const sections = ( + await Promise.all( + REFERENCE_SKILL_IDS.map((id) => session.skillsLoader.getSkill(id)), + ) + ).filter((content) => content.length > 0); + if (sections.length === 0) return architectPrompt; + return `${architectPrompt}\n\n## Reference skills\n\n${sections.join("\n\n---\n\n")}`; +} + +export async function createPipelineArchitectAgent( + session: AgentSession, +): Promise { + const csom = createCsomTools(session.bridge); + const runTools = createRunTools(session.bridge); + const agent = new Agent({ + name: "pipeline-architect", + handoffDescription: `Design and build new pipelines (or new stages within an existing + pipeline) from a high-level goal. Can mutate the pipeline via CSOM tools and submit a run + after a successful build when the user asks. Asks the user for input when design choices + are ambiguous.`, + instructions: await buildInstructions(session), + tools: [...csom.allTools, runTools.submitPipelineRun], + model: requireOrchestratorModel(), + }); + attachObservabilityHooks(agent, session.emitStatus); + return agent; +} diff --git a/src/agent/agents/tangleDispatcher.ts b/src/agent/agents/tangleDispatcher.ts index d20155214..4db6e5d44 100644 --- a/src/agent/agents/tangleDispatcher.ts +++ b/src/agent/agents/tangleDispatcher.ts @@ -21,6 +21,7 @@ import dispatcherPrompt from "../prompts/dispatcher.md?raw"; import type { AgentSession } from "../session"; import { createDebugAssistantAgent } from "./subagents/debugAssistant"; import { createGeneralHelpAgent } from "./subagents/generalHelp"; +import { createPipelineArchitectAgent } from "./subagents/pipelineArchitect"; import { createPipelineRepairAgent } from "./subagents/pipelineRepair"; interface DispatcherInvokeParams { @@ -39,9 +40,10 @@ export interface TangleDispatcher { invoke(params: DispatcherInvokeParams): Promise; } -function createDispatcherAgent(session: AgentSession): Agent { +async function createDispatcherAgent(session: AgentSession): Promise { const generalHelp = createGeneralHelpAgent(session); const pipelineRepair = createPipelineRepairAgent(session); + const pipelineArchitect = await createPipelineArchitectAgent(session); const debugAssistant = createDebugAssistantAgent(session); const agent = new Agent({ @@ -59,6 +61,11 @@ function createDispatcherAgent(session: AgentSession): Agent { toolDescription: "Ask the pipeline-repair specialist to inspect, validate, or fix the user's currently-open pipeline, or to apply a specific CSOM mutation directive. Can also submit a pipeline run after a successful fix when the user asked. Input: a clear directive. For open-ended repair use 'Validate and fix the current pipeline.'. For a targeted fix already identified by debug-assistant, pass the exact directive, e.g. 'Set the `label_column_name` input on [Train XGBoost model on CSV](entity://task-abc123) from \"unexistent\" to \"tips\".'. Add 'and resubmit the run' to the input only if the user explicitly asked to rerun.", }), + pipelineArchitect.asTool({ + toolName: "ask_pipeline_architect", + toolDescription: + "Ask the pipeline-architect specialist to design or build new pipeline structure — a whole pipeline from scratch, a new stage in an existing pipeline, or a multi-task subgraph. Can mutate the pipeline via CSOM tools and submit a run after a successful build when the user asked. NOT for fixing validation errors or single-task tweaks (use `ask_pipeline_repair`). Input: a clear design directive, e.g. 'Build a pipeline that loads a CSV, trains an XGBoost model on the `tips` column, and exposes the trained model as a pipeline output.'. Add 'and submit the run' to the input only if the user explicitly asked to run.", + }), debugAssistant.asTool({ toolName: "ask_debug_assistant", toolDescription: @@ -85,7 +92,7 @@ export function createDispatcher(): TangleDispatcher { async invoke(params) { params.session.proxyClient.ensureConfigured(params.token); const sessionMemory = getOrCreateSessionMemory(params.threadId); - const agent = createDispatcherAgent(params.session); + const agent = await createDispatcherAgent(params.session); const result = await run(agent, params.message, { session: sessionMemory, }); diff --git a/src/agent/config.ts b/src/agent/config.ts index 3d4210a0d..a310e314e 100644 --- a/src/agent/config.ts +++ b/src/agent/config.ts @@ -25,6 +25,7 @@ export const config = aiAssistantConfig as { orchestratorModel: string; subagentModel: string; embeddingModel: string; + skillsBaseUrl: string; }; function requireProxyBaseUrl(): string { @@ -63,6 +64,15 @@ export function requireEmbeddingModel(): string { return config.embeddingModel; } +export function requireSkillsBaseUrl(): string { + if (!config.skillsBaseUrl) { + throw new Error( + "AI assistant: skillsBaseUrl is empty. Set it in src/config/aiAssistantConfig.json.", + ); + } + return config.skillsBaseUrl; +} + /** * Read-only seam used by tools (e.g. `searchDocs`) that need the * configured `OpenAI` client. `ProxyClient` implements this; tests can diff --git a/src/agent/middleware/observability.ts b/src/agent/middleware/observability.ts index 1844cc9ca..05b1b570a 100644 --- a/src/agent/middleware/observability.ts +++ b/src/agent/middleware/observability.ts @@ -46,6 +46,7 @@ const TOOL_STATUS_LABELS: Record = { // the dispatcher has no handoffs anymore. ask_general_help: "Looking up information...", ask_pipeline_repair: "Asking pipeline-repair...", + ask_pipeline_architect: "Designing pipeline...", ask_debug_assistant: "Analyzing run failure...", }; diff --git a/src/agent/prompts/architect.md b/src/agent/prompts/architect.md new file mode 100644 index 000000000..d8e6644f9 --- /dev/null +++ b/src/agent/prompts/architect.md @@ -0,0 +1,83 @@ +# Pipeline Architect — System Prompt + +You are the **Pipeline Architect** specialist for Tangle Pipeline Studio. Your job is to design and build new pipelines — or new stages within an existing pipeline — from a high-level user goal. You translate intent into a concrete graph of tasks, bindings, inputs, and outputs. + +## Your Workflow + +1. Call `get_pipeline_state` first to understand whether the canvas is empty or already contains tasks. Note the existing tasks' `$id`, `name`, and component `inputs` / `outputs` so you can wire new structure into the right ports. +2. **Plan before mutating.** Sketch the target graph in your head (or as a short bulleted plan in your reply) before issuing any CSOM tool calls. Identify: + - The stages the user described (e.g. ingest, preprocess, train, evaluate). + - Which existing tasks (if any) cover those stages. + - The new tasks you need to add, the pipeline-level inputs the user must configure at run time, and the pipeline-level outputs that should be exposed. +3. **Build incrementally.** Add tasks and pipeline-level I/O first, then wire bindings, then set literal arguments. Call `validate_pipeline` after major edits and before finishing. +4. For ambiguous decisions (which dataset format? which evaluation metric? which output to expose?), ask the user one clear question instead of guessing. Do not invent component names, IDs, or input/output ports. +5. When the design is structurally sound, summarize what you built using the entity-link summary format below. + +## Component-availability constraint (beta) + +Component lookup is not yet wired into the architect (it lands in a later release alongside `search_components`). Until then: + +- You can freely **add, rename, delete, connect, and configure** tasks whose components are already referenced somewhere in the current pipeline spec — those `componentRef` payloads are visible in `get_pipeline_state`. +- You cannot conjure brand-new components from thin air. If the user asks for a stage that needs a component the spec does not already reference, say so plainly, suggest the closest existing component, and ask whether the user wants to add the component manually first. +- For an empty canvas, ask the user which components they want to start from (or which template), rather than fabricating tasks the system cannot resolve. + +## Subgraph design + +When the pipeline grows beyond a handful of tasks, group related work into subgraphs (`create_subgraph`) so the canvas stays readable. Follow the guidance in the `## Reference skills` section below (when present): each subgraph should represent one logical stage, stay under ~7 inner tasks, and have a descriptive human-readable name. Never wrap a single task in a subgraph. + +## Submitting runs + +You have access to `submit_pipeline_run`, which submits the current pipeline to the backend. Use it ONLY when: + +1. The dispatcher's `input` explicitly asked to run, rerun, submit, or "build it and run it" (typically the input ends with "and submit the run."). +2. You have completed your edits and the most recent `validate_pipeline` call returned no errors. If validation still has errors, do not submit; explain what is still broken so the user can resolve it. + +`submit_pipeline_run` takes no arguments — it always submits whatever pipeline is currently open. After a successful submission, include the returned `runId` in your summary so the dispatcher can mention it to the user. + +## CSOM Entity Model + +- **Tasks** — nodes referencing components, each with `$id`, `name`, `componentRef`. +- **Inputs** — pipeline-level input ports with `$id`, `name`, `type`. +- **Outputs** — pipeline-level output ports with `$id`, `name`, `type`. +- **Bindings** — directed edges from source entity/port to target entity/port. + +Every entity has a stable `$id`. Use these IDs when referencing entities in tool calls. + +## Active subgraph context + +`get_pipeline_state` may include an `activeSubgraphPath` field — a breadcrumb of subgraph task names from the root pipeline to whatever subgraph the user is currently viewing. Treat this as a hint about where the user's attention is, but remember: every CSOM mutation always applies to the root spec. When you build new structure, prefer extending the root pipeline (or an explicit subgraph the user named) rather than the nested view the user happens to be focused on. + +## When to defer to another specialist + +- Targeted edits to fix validation errors in an existing pipeline → defer to **pipeline-repair**. +- Diagnosing a failed pipeline run → defer to **debug-assistant**. +- General product / docs questions → defer to **general-help**. + +You build new structure. Repair fixes existing structure. The dispatcher routes; if you find yourself about to make a single-task tweak to fix a validation error, stop and explain that pipeline-repair is the right specialist for that. + +## Response Formatting + +When referring to pipeline entities (tasks, inputs, outputs) in your response, use this markdown link format so the UI can render them as interactive chips: + +``` +[Entity Name](entity://$id) +``` + +Examples: + +- "Added [Load CSV](entity://task-abc123) to ingest the training data." +- "Wired the model output to [trained_model](entity://output-xyz789)." + +After applying changes, include a summary using entity links: + +``` +## Pipeline Built +- Ingest: [Load CSV](entity://task-abc) +- Preprocess: [Normalize Features](entity://task-def) +- Train: [Train XGBoost](entity://task-ghi) +- Exposed [trained_model](entity://output-xyz) for downstream consumers. +``` + +## Response Style + +Be deliberate and transparent. State your plan first, then execute it, then summarize what you built. If you had to make an assumption (e.g. "I'm assuming the input CSV has a `label` column"), call it out so the user can correct you in the next turn. diff --git a/src/agent/prompts/dispatcher.md b/src/agent/prompts/dispatcher.md index daa745a2c..71f821243 100644 --- a/src/agent/prompts/dispatcher.md +++ b/src/agent/prompts/dispatcher.md @@ -6,13 +6,12 @@ You are the **Tangle Dispatcher**, the entry point for the Tangle Pipeline Studi Each specialist is exposed to you as a tool. Calling a tool runs the specialist's own sub-agent loop and returns its final response as a string. -| Tool | When to call it | -| --------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `ask_general_help` | Any question about Tangle concepts, features, how things work, best practices, getting started, or documentation lookups (e.g. "what is a pipeline?", "how do I connect tasks?", "what are subgraphs?"). | -| `ask_pipeline_repair` | Any request to inspect, validate, or fix the user's current pipeline — broken connections, validation errors, dangling bindings, missing arguments, structural cleanup — and "fix it and run it". Also call this with a specific directive when `ask_debug_assistant` has already identified a concrete fix to apply. | -| `ask_debug_assistant` | Any request to diagnose or explain a **failed run**: "why did my run fail?", "what went wrong with run 12345?", "show me the error from the latest run". Read-only — it does not edit the spec or rerun. | - -A future release will add an architect specialist for building new pipelines from scratch. +| Tool | When to call it | +| ------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `ask_general_help` | Any question about Tangle concepts, features, how things work, best practices, getting started, or documentation lookups (e.g. "what is a pipeline?", "how do I connect tasks?", "what are subgraphs?"). | +| `ask_pipeline_repair` | Any request to inspect, validate, or fix the user's current pipeline — broken connections, validation errors, dangling bindings, missing arguments, structural cleanup — and "fix it and run it". Also call this with a specific directive when `ask_debug_assistant` has already identified a concrete fix to apply. | +| `ask_pipeline_architect` | Any request to **design or build new pipeline structure** — a whole pipeline from scratch ("build me a pipeline that…"), a new stage in an existing pipeline ("add a training stage"), or a multi-task subgraph. Can also "build it and run it". NOT for fixing validation errors or single-task tweaks. | +| `ask_debug_assistant` | Any request to diagnose or explain a **failed run**: "why did my run fail?", "what went wrong with run 12345?", "show me the error from the latest run". Read-only — it does not edit the spec or rerun. | ## Calling a specialist @@ -20,7 +19,8 @@ When you call a tool, the `input` field is the prompt that the sub-agent will se - For routing a single request, restate the user's ask clearly. Include any run id, task name, or run-verb context the specialist will need. Example: `input: "Why did the latest run fail?"`. - For a targeted fix derived from a previous tool result, pass an unambiguous directive: `input: "Set the \`label_column_name\` input on [Train XGBoost model on CSV](entity://task-abc123) from \"unexistent\" to \"tips\"."`. -- If the user explicitly asked to run/rerun, append "and resubmit the run." to the `ask_pipeline_repair` input. Do not add this otherwise. +- For `ask_pipeline_architect`, hand off the user's design intent in their words plus any constraints they stated (data shape, target metric, components they already named). +- If the user explicitly asked to run/rerun, append "and resubmit the run." to the `ask_pipeline_repair` input or "and submit the run." to the `ask_pipeline_architect` input. Do not add this otherwise. ## Multi-step orchestration @@ -31,6 +31,13 @@ Some user requests need more than one specialist in a single turn. Chain tool ca - **Diagnosis is ambiguous** — if the debug-assistant says the fix needs user input or spans multiple tasks, do NOT call `ask_pipeline_repair`. Return the diagnosis as-is and let the user pick the next step. - **User only asked "why did it fail"** — call `ask_debug_assistant` once and return its output. Do not infer a fix request. +### Architect vs repair + +- New structure ("build me a pipeline that…", "add a training stage", "design a subgraph for preprocessing") → `ask_pipeline_architect`. +- Edits to existing structure to make it work ("fix validation", "the connection is broken", "this input is wrong") → `ask_pipeline_repair`. +- A single new task added to an otherwise working pipeline → `ask_pipeline_repair` (it owns small targeted CSOM mutations). +- Genuinely ambiguous (e.g. "make this pipeline work") → prefer `ask_pipeline_repair` and let it ask the user a clarifying question if needed. + ## Returning tool output When a specialist tool returns, **relay its response** to the user. Specialists emit interactive entity links the UI renders as chips: diff --git a/src/agent/session.ts b/src/agent/session.ts index 17e5491ab..2f01f24c1 100644 --- a/src/agent/session.ts +++ b/src/agent/session.ts @@ -2,6 +2,7 @@ * Per-request session for the in-browser agent. */ import type { ProxyClient } from "./config"; +import type { SkillsLoader } from "./skills/loader"; import type { ToolBridgeApi } from "./toolBridgeApi"; import type { StatusCallback } from "./types"; @@ -18,6 +19,7 @@ export interface AgentSession { emitStatus: StatusCallback; proxyClient: ProxyClient; bridge: ToolBridgeApi; + skillsLoader: SkillsLoader; recentRuns: RecentPipelineRun[]; } @@ -25,6 +27,7 @@ export function createSession(params: { threadId: string; proxyClient: ProxyClient; bridge: ToolBridgeApi; + skillsLoader: SkillsLoader; emitStatus?: StatusCallback; recentRuns?: RecentPipelineRun[]; }): AgentSession { @@ -33,6 +36,7 @@ export function createSession(params: { emitStatus: params.emitStatus ?? (() => {}), proxyClient: params.proxyClient, bridge: params.bridge, + skillsLoader: params.skillsLoader, recentRuns: params.recentRuns ?? [], }; } diff --git a/src/agent/skills/loader.ts b/src/agent/skills/loader.ts new file mode 100644 index 000000000..803c4fe6c --- /dev/null +++ b/src/agent/skills/loader.ts @@ -0,0 +1,47 @@ +/** + * Skills loader for the in-browser agent. + * + * Loads a `SKILL.md` document over HTTP from `${skillsBaseUrl}/${id}/SKILL.md` + * and persists the content in `agentDb.skills` so subsequent worker sessions + * start warm. Cache freshness is keyed off `GIT_COMMIT.substring(0, 6)` — + * skills change only when the app is deployed, so the build SHA prefix is + * sufficient as a version marker. + */ +import { GIT_COMMIT } from "@/utils/constants"; + +import { requireSkillsBaseUrl } from "../config"; +import { agentDb, type SkillEntry } from "../idb/agentDb"; + +const SKILLS_VERSION = GIT_COMMIT.substring(0, 6); + +export class SkillsLoader { + readonly #skills = new Map>(); + + getSkill(skillId: string): Promise { + const existing = this.#skills.get(skillId); + if (existing) return existing; + const promise = this.#resolve(skillId); + this.#skills.set(skillId, promise); + return promise; + } + + async #resolve(skillId: string): Promise { + const row = await agentDb.skills.get(skillId); + if (row && row.version === SKILLS_VERSION) return row.content; + try { + const url = `${requireSkillsBaseUrl()}/${skillId}/SKILL.md`; + const response = await fetch(url); + if (!response.ok) throw new Error(`HTTP ${response.status}`); + const content = await response.text(); + const entry: SkillEntry = { + id: skillId, + version: SKILLS_VERSION, + content, + }; + await agentDb.skills.put(entry); + return content; + } catch { + return row?.content ?? ""; + } + } +} diff --git a/src/agent/worker.ts b/src/agent/worker.ts index 0566d23c5..c511fdbec 100644 --- a/src/agent/worker.ts +++ b/src/agent/worker.ts @@ -47,6 +47,7 @@ import { import { getAiToken } from "./aiTokenStore"; import { ProxyClient } from "./config"; import { createSession, type RecentPipelineRun } from "./session"; +import { SkillsLoader } from "./skills/loader"; import type { ToolBridgeApi } from "./toolBridgeApi"; import type { AgentResponse, StatusCallback } from "./types"; @@ -71,6 +72,7 @@ function createWorkerApi(): AgentWorkerApi { let bridge: ToolBridgeApi | null = null; let emitStatus: StatusCallback = () => {}; const proxyClient = new ProxyClient(); + const skillsLoader = new SkillsLoader(); return { /** @@ -108,6 +110,7 @@ function createWorkerApi(): AgentWorkerApi { emitStatus, proxyClient, bridge, + skillsLoader, recentRuns, });