agent-worker 0.16.0 → 0.17.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.
@@ -2,10 +2,10 @@
2
2
  import { A as parseModel, I as getDefaultModel, L as isAutoProvider, P as createModelAsync, R as resolveModelFallback, a as createMockBackend, j as FRONTIER_MODELS, k as normalizeBackendType, n as createBackend } from "../backends-D7DT0uox.mjs";
3
3
  import { t as createTool } from "../create-tool-gcUuI1FD.mjs";
4
4
  import { generateText, stepCountIs, tool } from "ai";
5
- import { existsSync, mkdirSync, readFileSync, readdirSync, unlinkSync, writeFileSync } from "node:fs";
6
- import { dirname, isAbsolute, join, relative } from "node:path";
5
+ import { existsSync, mkdirSync, readFileSync, readdirSync, rmSync, unlinkSync, writeFileSync } from "node:fs";
6
+ import { basename, dirname, isAbsolute, join, relative, resolve } from "node:path";
7
7
  import { appendFile, mkdir, open, readFile, readdir, stat, unlink, writeFile } from "node:fs/promises";
8
- import { stringify } from "yaml";
8
+ import { parse, stringify } from "yaml";
9
9
  import { z } from "zod";
10
10
  import { homedir } from "node:os";
11
11
  import { execSync, spawn } from "node:child_process";
@@ -21,6 +21,7 @@ import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/
21
21
  import { MockLanguageModelV3, mockValues } from "ai/test";
22
22
  import { Client } from "@modelcontextprotocol/sdk/client/index.js";
23
23
  import { StreamableHTTPClientTransport } from "@modelcontextprotocol/sdk/client/streamableHttp.js";
24
+ import { z as z$1 } from "zod/v4";
24
25
 
25
26
  //#region rolldown:runtime
26
27
  var __defProp = Object.defineProperty;
@@ -1709,76 +1710,106 @@ function formatInbox(inbox) {
1709
1710
  return `- [${time}] From @${m.entry.from}${priority}${dm}: ${m.entry.content}`;
1710
1711
  }).join("\n");
1711
1712
  }
1713
+ /** Project context (what codebase to work on) */
1714
+ const projectSection = (ctx) => `## Project\nWorking on: ${ctx.projectDir}`;
1715
+ /** Inbox (unread messages for this agent) */
1716
+ const inboxSection = (ctx) => {
1717
+ const count = ctx.inbox.length;
1718
+ return `## Inbox (${count} ${count === 1 ? "message" : "messages"} for you)\n${formatInbox(ctx.inbox)}`;
1719
+ };
1720
+ /** Recent activity hint (use tool instead of injecting messages) */
1721
+ const activitySection = () => "## Recent Activity\nUse channel_read tool to view recent channel messages and conversation context if needed.";
1722
+ /** Shared document section */
1723
+ const documentSection = (ctx) => ctx.documentContent ? `## Shared Document\n${ctx.documentContent}` : null;
1724
+ /** Retry notice */
1725
+ const retrySection = (ctx) => ctx.retryAttempt > 1 ? `## Note\nThis is retry attempt ${ctx.retryAttempt}. Previous attempt failed.` : null;
1726
+ /** MCP tool instructions */
1727
+ const instructionsSection = (ctx) => {
1728
+ const lines = [];
1729
+ lines.push("## Instructions");
1730
+ lines.push("You are an agent in a multi-agent workflow. Communicate ONLY through the MCP tools below.");
1731
+ lines.push("Your text output is NOT seen by other agents — you MUST use channel_send to communicate.");
1732
+ lines.push("");
1733
+ lines.push("### Channel Tools");
1734
+ lines.push("- **channel_send**: Send a message to the shared channel. Use @agentname to mention/notify.");
1735
+ lines.push(" Use the \"to\" parameter for private DMs: channel_send({ message: \"...\", to: \"bob\" })");
1736
+ lines.push("- **channel_read**: Read recent channel messages (DMs and logs are auto-filtered).");
1737
+ lines.push("");
1738
+ lines.push("### Team Tools");
1739
+ lines.push("- **team_members**: List all agents you can @mention. Pass includeStatus=true to see their current state and tasks.");
1740
+ lines.push("- **team_doc_read/write/append/list/create**: Shared team documents.");
1741
+ lines.push("");
1742
+ lines.push("### Personal Tools");
1743
+ lines.push("- **my_inbox**: Check your unread messages.");
1744
+ lines.push("- **my_inbox_ack**: Acknowledge messages after processing (pass the latest message ID).");
1745
+ lines.push("- **my_status_set**: Update your status. Call when starting work (state='running', task='...') or when done (state='idle').");
1746
+ lines.push("");
1747
+ lines.push("### Proposal & Voting Tools");
1748
+ lines.push("- **team_proposal_create**: Create a proposal for team voting (types: election, decision, approval, assignment).");
1749
+ lines.push("- **team_vote**: Cast your vote on an active proposal. You can change your vote by voting again.");
1750
+ lines.push("- **team_proposal_status**: Check status of a proposal, or list all active proposals.");
1751
+ lines.push("- **team_proposal_cancel**: Cancel a proposal you created.");
1752
+ lines.push("");
1753
+ lines.push("### Resource Tools");
1754
+ lines.push("- **resource_create**: Store large content, get a reference (resource:id) for use anywhere.");
1755
+ lines.push("- **resource_read**: Read resource content by ID.");
1756
+ if (ctx.feedback) {
1757
+ lines.push("");
1758
+ lines.push("### Feedback Tool");
1759
+ lines.push("- **feedback_submit**: Report workflow improvement needs — a missing tool, an awkward step, or a capability gap.");
1760
+ lines.push(" Only use when you genuinely hit a pain point during your work.");
1761
+ }
1762
+ return lines.join("\n");
1763
+ };
1764
+ /** Workflow instructions (read → work → ack → exit) */
1765
+ const workflowSection = () => {
1766
+ const lines = [];
1767
+ lines.push("### Workflow");
1768
+ lines.push("1. Read your inbox messages above");
1769
+ lines.push("2. Do your assigned work using channel_send with @mentions");
1770
+ lines.push("3. Acknowledge your inbox with my_inbox_ack");
1771
+ lines.push("4. Exit when your task is complete");
1772
+ return lines.join("\n");
1773
+ };
1774
+ /** Exit guidance (when to stop) */
1775
+ const exitSection = () => {
1776
+ const lines = [];
1777
+ lines.push("### IMPORTANT: When to stop");
1778
+ lines.push("- Once your assigned task is complete, acknowledge your inbox and exit. Do NOT keep chatting.");
1779
+ lines.push("- Do NOT send pleasantries (\"you're welcome\", \"glad to help\", \"thanks again\") — they trigger unnecessary cycles.");
1780
+ lines.push("- Do NOT @mention another agent in your final message unless you need them to do more work.");
1781
+ lines.push("- If you receive a thank-you or acknowledgment, just call my_inbox_ack and exit. Do not reply.");
1782
+ return lines.join("\n");
1783
+ };
1712
1784
  /**
1713
- * Build the complete agent prompt from run context
1785
+ * Default prompt sections — produces the same output as the original
1786
+ * monolithic buildAgentPrompt. New sections (soul, memory, todo) can
1787
+ * be inserted at specific positions without touching these.
1788
+ */
1789
+ const DEFAULT_SECTIONS = [
1790
+ projectSection,
1791
+ inboxSection,
1792
+ activitySection,
1793
+ documentSection,
1794
+ retrySection,
1795
+ instructionsSection,
1796
+ workflowSection,
1797
+ exitSection
1798
+ ];
1799
+ /**
1800
+ * Assemble prompt from sections. Joins non-null sections with blank lines.
1801
+ */
1802
+ function assemblePrompt(sections, ctx) {
1803
+ return sections.map((section) => section(ctx)).filter((content) => content !== null).join("\n\n");
1804
+ }
1805
+ /**
1806
+ * Build the complete agent prompt from run context.
1807
+ *
1808
+ * Uses the default section list. For custom section lists,
1809
+ * use assemblePrompt() directly.
1714
1810
  */
1715
1811
  function buildAgentPrompt(ctx) {
1716
- const sections = [];
1717
- sections.push("## Project");
1718
- sections.push(`Working on: ${ctx.projectDir}`);
1719
- sections.push("");
1720
- sections.push(`## Inbox (${ctx.inbox.length} message${ctx.inbox.length === 1 ? "" : "s"} for you)`);
1721
- sections.push(formatInbox(ctx.inbox));
1722
- sections.push("");
1723
- sections.push("## Recent Activity");
1724
- sections.push("Use channel_read tool to view recent channel messages and conversation context if needed.");
1725
- if (ctx.documentContent) {
1726
- sections.push("");
1727
- sections.push("## Shared Document");
1728
- sections.push(ctx.documentContent);
1729
- }
1730
- if (ctx.retryAttempt > 1) {
1731
- sections.push("");
1732
- sections.push(`## Note`);
1733
- sections.push(`This is retry attempt ${ctx.retryAttempt}. Previous attempt failed.`);
1734
- }
1735
- sections.push("");
1736
- sections.push("## Instructions");
1737
- sections.push("You are an agent in a multi-agent workflow. Communicate ONLY through the MCP tools below.");
1738
- sections.push("Your text output is NOT seen by other agents — you MUST use channel_send to communicate.");
1739
- sections.push("");
1740
- sections.push("### Channel Tools");
1741
- sections.push("- **channel_send**: Send a message to the shared channel. Use @agentname to mention/notify.");
1742
- sections.push(" Use the \"to\" parameter for private DMs: channel_send({ message: \"...\", to: \"bob\" })");
1743
- sections.push("- **channel_read**: Read recent channel messages (DMs and logs are auto-filtered).");
1744
- sections.push("");
1745
- sections.push("### Team Tools");
1746
- sections.push("- **team_members**: List all agents you can @mention. Pass includeStatus=true to see their current state and tasks.");
1747
- sections.push("- **team_doc_read/write/append/list/create**: Shared team documents.");
1748
- sections.push("");
1749
- sections.push("### Personal Tools");
1750
- sections.push("- **my_inbox**: Check your unread messages.");
1751
- sections.push("- **my_inbox_ack**: Acknowledge messages after processing (pass the latest message ID).");
1752
- sections.push("- **my_status_set**: Update your status. Call when starting work (state='running', task='...') or when done (state='idle').");
1753
- sections.push("");
1754
- sections.push("### Proposal & Voting Tools");
1755
- sections.push("- **team_proposal_create**: Create a proposal for team voting (types: election, decision, approval, assignment).");
1756
- sections.push("- **team_vote**: Cast your vote on an active proposal. You can change your vote by voting again.");
1757
- sections.push("- **team_proposal_status**: Check status of a proposal, or list all active proposals.");
1758
- sections.push("- **team_proposal_cancel**: Cancel a proposal you created.");
1759
- sections.push("");
1760
- sections.push("### Resource Tools");
1761
- sections.push("- **resource_create**: Store large content, get a reference (resource:id) for use anywhere.");
1762
- sections.push("- **resource_read**: Read resource content by ID.");
1763
- if (ctx.feedback) {
1764
- sections.push("");
1765
- sections.push("### Feedback Tool");
1766
- sections.push("- **feedback_submit**: Report workflow improvement needs — a missing tool, an awkward step, or a capability gap.");
1767
- sections.push(" Only use when you genuinely hit a pain point during your work.");
1768
- }
1769
- sections.push("");
1770
- sections.push("### Workflow");
1771
- sections.push("1. Read your inbox messages above");
1772
- sections.push("2. Do your assigned work using channel_send with @mentions");
1773
- sections.push("3. Acknowledge your inbox with my_inbox_ack");
1774
- sections.push("4. Exit when your task is complete");
1775
- sections.push("");
1776
- sections.push("### IMPORTANT: When to stop");
1777
- sections.push("- Once your assigned task is complete, acknowledge your inbox and exit. Do NOT keep chatting.");
1778
- sections.push("- Do NOT send pleasantries (\"you're welcome\", \"glad to help\", \"thanks again\") — they trigger unnecessary cycles.");
1779
- sections.push("- Do NOT @mention another agent in your final message unless you need them to do more work.");
1780
- sections.push("- If you receive a thank-you or acknowledgment, just call my_inbox_ack and exit. Do not reply.");
1781
- return sections.join("\n");
1812
+ return assemblePrompt(DEFAULT_SECTIONS, ctx);
1782
1813
  }
1783
1814
 
1784
1815
  //#endregion
@@ -2849,6 +2880,12 @@ var daemon_exports = /* @__PURE__ */ __exportAll({
2849
2880
  createDaemonApp: () => createDaemonApp,
2850
2881
  startDaemon: () => startDaemon
2851
2882
  });
2883
+ /** Key prefix for standalone agent workflow handles */
2884
+ const STANDALONE_PREFIX = "standalone:";
2885
+ /** Build a workflow key for standalone agents */
2886
+ function standaloneKey(agentName) {
2887
+ return `${STANDALONE_PREFIX}${agentName}`;
2888
+ }
2852
2889
  let state = null;
2853
2890
  let shuttingDown = false;
2854
2891
  const mcpSessions = /* @__PURE__ */ new Map();
@@ -2856,6 +2893,10 @@ async function gracefulShutdown() {
2856
2893
  if (shuttingDown) return;
2857
2894
  shuttingDown = true;
2858
2895
  if (state) {
2896
+ for (const [, loop] of state.loops) try {
2897
+ await loop.stop();
2898
+ } catch {}
2899
+ state.loops.clear();
2859
2900
  for (const [, wf] of state.workflows) try {
2860
2901
  await wf.shutdown();
2861
2902
  } catch {}
@@ -2877,8 +2918,8 @@ async function parseJsonBody(c) {
2877
2918
  return null;
2878
2919
  }
2879
2920
  }
2880
- /** Map AgentConfig to the ResolvedAgent type needed by the factory */
2881
- function configToResolvedAgent(cfg) {
2921
+ /** Map AgentConfig to the ResolvedWorkflowAgent type needed by the factory */
2922
+ function configToResolvedWorkflowAgent(cfg) {
2882
2923
  return {
2883
2924
  backend: cfg.backend,
2884
2925
  model: cfg.model,
@@ -2888,10 +2929,16 @@ function configToResolvedAgent(cfg) {
2888
2929
  };
2889
2930
  }
2890
2931
  /**
2891
- * Find an agent's loop across all workflows.
2892
- * Returns the loop if the agent exists in any workflow.
2932
+ * Find an agent's loop.
2933
+ * First checks daemon-level loops (standalone agents),
2934
+ * then falls back to workflow-scoped loops (workflow agents).
2893
2935
  */
2894
2936
  function findLoop(s, agentName) {
2937
+ const daemonLoop = s.loops.get(agentName);
2938
+ if (daemonLoop) return {
2939
+ loop: daemonLoop,
2940
+ workflow: s.workflows.get(standaloneKey(agentName)) ?? null
2941
+ };
2895
2942
  for (const wf of s.workflows.values()) {
2896
2943
  const l = wf.loops.get(agentName);
2897
2944
  if (l) return {
@@ -2902,9 +2949,12 @@ function findLoop(s, agentName) {
2902
2949
  return null;
2903
2950
  }
2904
2951
  /**
2905
- * Ensure a standalone agent has a workflow + loop.
2952
+ * Ensure a standalone agent has a loop + runtime.
2906
2953
  * Creates the infrastructure lazily on first call (starts MCP server, etc.).
2907
2954
  *
2955
+ * The loop is stored in `s.loops` (daemon-owned).
2956
+ * A WorkflowHandle is still created for runtime resource management (MCP, context).
2957
+ *
2908
2958
  * This is the bridge between POST /agents (stores config only) and
2909
2959
  * POST /run or /serve (needs a loop to execute).
2910
2960
  */
@@ -2913,11 +2963,13 @@ async function ensureAgentLoop(s, agentName) {
2913
2963
  if (existing) return existing;
2914
2964
  const cfg = s.configs.get(agentName);
2915
2965
  if (!cfg) throw new Error(`Agent not found: ${agentName}`);
2916
- const agentDef = configToResolvedAgent(cfg);
2917
- const wfKey = `standalone:${agentName}`;
2966
+ const agentDef = configToResolvedWorkflowAgent(cfg);
2967
+ const wfKey = standaloneKey(agentName);
2968
+ const workflowName = cfg.workflow ?? "global";
2969
+ const workflowTag = cfg.tag ?? "main";
2918
2970
  const runtime = await createMinimalRuntime({
2919
- workflowName: cfg.workflow,
2920
- tag: cfg.tag,
2971
+ workflowName,
2972
+ tag: workflowTag,
2921
2973
  agentNames: [agentName]
2922
2974
  });
2923
2975
  let loop;
@@ -2931,10 +2983,12 @@ async function ensureAgentLoop(s, agentName) {
2931
2983
  await runtime.shutdown();
2932
2984
  throw err;
2933
2985
  }
2986
+ s.loops.set(agentName, loop);
2934
2987
  const handle = {
2935
- name: cfg.workflow,
2936
- tag: cfg.tag,
2988
+ name: workflowName,
2989
+ tag: workflowTag,
2937
2990
  key: wfKey,
2991
+ standalone: true,
2938
2992
  agents: [agentName],
2939
2993
  loops: new Map([[agentName, loop]]),
2940
2994
  contextProvider: runtime.contextProvider,
@@ -2978,7 +3032,7 @@ function createDaemonApp(options) {
2978
3032
  const s = getState();
2979
3033
  if (!s) return c.json({ status: "unavailable" }, 503);
2980
3034
  const standaloneAgents = [...s.configs.keys()];
2981
- const workflowList = [...s.workflows.values()].map((wf) => ({
3035
+ const workflowList = [...s.workflows.values()].filter((wf) => !wf.standalone).map((wf) => ({
2982
3036
  name: wf.name,
2983
3037
  tag: wf.tag,
2984
3038
  agents: wf.agents
@@ -3032,7 +3086,7 @@ function createDaemonApp(options) {
3032
3086
  if (!s) return c.json({ error: "Not ready" }, 503);
3033
3087
  const body = await parseJsonBody(c);
3034
3088
  if (!body || typeof body !== "object") return c.json({ error: "Invalid JSON body" }, 400);
3035
- const { name, model, system, backend = "default", provider, workflow = "global", tag = "main", schedule } = body;
3089
+ const { name, model, system, backend = "default", provider, workflow, tag, schedule } = body;
3036
3090
  if (!name || !model || !system) return c.json({ error: "name, model, system required" }, 400);
3037
3091
  if (s.configs.has(name)) return c.json({ error: `Agent already exists: ${name}` }, 409);
3038
3092
  const agentConfig = {
@@ -3077,7 +3131,14 @@ function createDaemonApp(options) {
3077
3131
  if (!s) return c.json({ error: "Not ready" }, 503);
3078
3132
  const name = c.req.param("name");
3079
3133
  if (!s.configs.delete(name)) return c.json({ error: "Agent not found" }, 404);
3080
- const wfKey = `standalone:${name}`;
3134
+ const daemonLoop = s.loops.get(name);
3135
+ if (daemonLoop) {
3136
+ try {
3137
+ await daemonLoop.stop();
3138
+ } catch {}
3139
+ s.loops.delete(name);
3140
+ }
3141
+ const wfKey = standaloneKey(name);
3081
3142
  const wf = s.workflows.get(wfKey);
3082
3143
  if (wf) {
3083
3144
  try {
@@ -3231,7 +3292,7 @@ function createDaemonApp(options) {
3231
3292
  const key = `${workflowName}:${tag}`;
3232
3293
  if (s.workflows.has(key)) return c.json({ error: `Workflow already running: ${key}` }, 409);
3233
3294
  try {
3234
- const { runWorkflowWithLoops } = await import("../runner-IkYhcbio.mjs");
3295
+ const { runWorkflowWithLoops } = await import("../runner-BmT0Y8MD.mjs");
3235
3296
  const result = await runWorkflowWithLoops({
3236
3297
  workflow,
3237
3298
  workflowName,
@@ -3270,7 +3331,7 @@ function createDaemonApp(options) {
3270
3331
  app.get("/workflows", (c) => {
3271
3332
  const s = getState();
3272
3333
  if (!s) return c.json({ error: "Not ready" }, 503);
3273
- const workflows = [...s.workflows.values()].map((wf) => {
3334
+ const workflows = [...s.workflows.values()].filter((wf) => !wf.standalone).map((wf) => {
3274
3335
  const agentStates = {};
3275
3336
  for (const [name, loop] of wf.loops) agentStates[name] = loop.state;
3276
3337
  return {
@@ -3334,6 +3395,7 @@ async function startDaemon(config = {}) {
3334
3395
  });
3335
3396
  state = {
3336
3397
  configs: /* @__PURE__ */ new Map(),
3398
+ loops: /* @__PURE__ */ new Map(),
3337
3399
  workflows: /* @__PURE__ */ new Map(),
3338
3400
  store,
3339
3401
  server,
@@ -3541,6 +3603,380 @@ function outputJson(data) {
3541
3603
  console.log(JSON.stringify(data, null, 2));
3542
3604
  }
3543
3605
 
3606
+ //#endregion
3607
+ //#region src/agent/definition.ts
3608
+ /**
3609
+ * AgentDefinition — Top-level persistent agent identity.
3610
+ *
3611
+ * This is the NEW AgentDefinition from AGENT-TOP-LEVEL architecture.
3612
+ * It describes WHO an agent is (prompt, soul, context) — not how it runs in a workflow.
3613
+ *
3614
+ * Loaded from .agents/*.yaml files. Workflows reference agents by name.
3615
+ *
3616
+ * Distinct from:
3617
+ * - WorkflowAgentDef (workflow/types.ts) — inline agent config within a workflow
3618
+ * - AgentConfig (agent/config.ts) — runtime config for daemon-created agents
3619
+ */
3620
+ /**
3621
+ * Standard subdirectories within an agent's context directory.
3622
+ * Created automatically when the agent is loaded.
3623
+ */
3624
+ const CONTEXT_SUBDIRS = [
3625
+ "memory",
3626
+ "notes",
3627
+ "conversations",
3628
+ "todo"
3629
+ ];
3630
+ const AgentSoulSchema = z$1.object({
3631
+ role: z$1.string().optional(),
3632
+ expertise: z$1.array(z$1.string()).optional(),
3633
+ style: z$1.string().optional(),
3634
+ principles: z$1.array(z$1.string()).optional()
3635
+ }).passthrough();
3636
+ const ProviderConfigSchema = z$1.object({
3637
+ name: z$1.string(),
3638
+ base_url: z$1.string().optional(),
3639
+ api_key: z$1.string().optional()
3640
+ }).passthrough();
3641
+ const AgentPromptConfigSchema = z$1.union([z$1.object({
3642
+ system: z$1.string(),
3643
+ system_file: z$1.undefined().optional()
3644
+ }), z$1.object({
3645
+ system_file: z$1.string(),
3646
+ system: z$1.undefined().optional()
3647
+ })]);
3648
+ const AgentContextConfigSchema = z$1.object({
3649
+ dir: z$1.string().optional(),
3650
+ thin_thread: z$1.number().int().min(1).optional()
3651
+ });
3652
+ const ScheduleConfigSchema = z$1.object({
3653
+ wakeup: z$1.union([z$1.string(), z$1.number()]),
3654
+ prompt: z$1.string().optional()
3655
+ });
3656
+ const AgentDefinitionSchema = z$1.object({
3657
+ name: z$1.string().min(1),
3658
+ model: z$1.string().min(1),
3659
+ backend: z$1.enum([
3660
+ "sdk",
3661
+ "claude",
3662
+ "cursor",
3663
+ "codex",
3664
+ "opencode",
3665
+ "mock"
3666
+ ]).optional(),
3667
+ provider: z$1.union([z$1.string(), ProviderConfigSchema]).optional(),
3668
+ prompt: AgentPromptConfigSchema,
3669
+ soul: AgentSoulSchema.optional(),
3670
+ context: AgentContextConfigSchema.optional(),
3671
+ max_tokens: z$1.number().int().positive().optional(),
3672
+ max_steps: z$1.number().int().positive().optional(),
3673
+ schedule: ScheduleConfigSchema.optional()
3674
+ });
3675
+
3676
+ //#endregion
3677
+ //#region src/agent/agent-handle.ts
3678
+ /**
3679
+ * AgentHandle — Runtime wrapper for an agent definition + persistent context.
3680
+ *
3681
+ * Created by AgentRegistry when an agent is loaded. Provides:
3682
+ * - Context directory management (memory/, notes/, conversations/, todo/)
3683
+ * - Read/write operations for personal context
3684
+ * - State tracking (idle, running, stopped, error)
3685
+ *
3686
+ * Phase 1 scope: context directory + read/write ops.
3687
+ * Phase 3 adds: loop, workspaces, threads.
3688
+ */
3689
+ var AgentHandle = class {
3690
+ /** Agent definition (from YAML) */
3691
+ definition;
3692
+ /** Absolute path to agent's persistent context directory */
3693
+ contextDir;
3694
+ /** Current agent state */
3695
+ state = "idle";
3696
+ constructor(definition, contextDir) {
3697
+ this.definition = definition;
3698
+ this.contextDir = contextDir;
3699
+ }
3700
+ /** Agent name (convenience accessor) */
3701
+ get name() {
3702
+ return this.definition.name;
3703
+ }
3704
+ /**
3705
+ * Ensure the context directory and all subdirectories exist.
3706
+ * Called on agent load/creation. Idempotent.
3707
+ */
3708
+ ensureContextDir() {
3709
+ for (const sub of CONTEXT_SUBDIRS) mkdirSync(join(this.contextDir, sub), { recursive: true });
3710
+ }
3711
+ /**
3712
+ * Read all memory entries as key-value records.
3713
+ * Memory files are YAML in memory/<key>.yaml.
3714
+ */
3715
+ async readMemory() {
3716
+ const memDir = join(this.contextDir, "memory");
3717
+ if (!existsSync(memDir)) return {};
3718
+ const result = {};
3719
+ const files = await readdir(memDir);
3720
+ for (const file of files) {
3721
+ if (!file.endsWith(".yaml") && !file.endsWith(".yml")) continue;
3722
+ const key = basename(file).replace(/\.ya?ml$/i, "");
3723
+ try {
3724
+ result[key] = parse(await readFile(join(memDir, file), "utf-8"));
3725
+ } catch (err) {
3726
+ console.warn(`Skipping malformed memory file ${file}:`, err);
3727
+ }
3728
+ }
3729
+ return result;
3730
+ }
3731
+ /**
3732
+ * Write a memory entry. Creates/overwrites memory/<key>.yaml.
3733
+ */
3734
+ async writeMemory(key, value) {
3735
+ const memDir = join(this.contextDir, "memory");
3736
+ await mkdir(memDir, { recursive: true });
3737
+ await writeFile(join(memDir, `${key}.yaml`), stringify(value));
3738
+ }
3739
+ /**
3740
+ * Read agent's notes, most recent first.
3741
+ * Notes are markdown files in notes/.
3742
+ */
3743
+ async readNotes(limit) {
3744
+ const notesDir = join(this.contextDir, "notes");
3745
+ if (!existsSync(notesDir)) return [];
3746
+ const files = (await readdir(notesDir)).filter((f) => f.endsWith(".md")).sort().reverse();
3747
+ const selected = limit ? files.slice(0, limit) : files;
3748
+ return Promise.all(selected.map((f) => readFile(join(notesDir, f), "utf-8")));
3749
+ }
3750
+ /**
3751
+ * Append a note. Creates notes/<date>-<slug>.md.
3752
+ */
3753
+ async appendNote(content, slug) {
3754
+ const notesDir = join(this.contextDir, "notes");
3755
+ await mkdir(notesDir, { recursive: true });
3756
+ const filename = `${(/* @__PURE__ */ new Date()).toISOString().slice(0, 10)}-${slug ?? `note-${Date.now().toString(36)}`}.md`;
3757
+ await writeFile(join(notesDir, filename), content);
3758
+ return filename;
3759
+ }
3760
+ /**
3761
+ * Read active todos from todo/index.md.
3762
+ * Returns lines that look like incomplete tasks: "- [ ] ..."
3763
+ */
3764
+ async readTodos() {
3765
+ const todoFile = join(this.contextDir, "todo", "index.md");
3766
+ if (!existsSync(todoFile)) return [];
3767
+ return (await readFile(todoFile, "utf-8")).split("\n").filter((line) => line.match(/^\s*-\s*\[\s*\]/)).map((line) => line.replace(/^\s*-\s*\[\s*\]\s*/, "").trim());
3768
+ }
3769
+ /**
3770
+ * Write the full todo list. Replaces todo/index.md.
3771
+ */
3772
+ async writeTodos(todos) {
3773
+ const todoDir = join(this.contextDir, "todo");
3774
+ await mkdir(todoDir, { recursive: true });
3775
+ const content = todos.map((t) => `- [ ] ${t}`).join("\n") + "\n";
3776
+ await writeFile(join(todoDir, "index.md"), content);
3777
+ }
3778
+ };
3779
+
3780
+ //#endregion
3781
+ //#region src/agent/yaml-parser.ts
3782
+ /**
3783
+ * Agent YAML Parser — Load agent definitions from .agents/*.yaml files.
3784
+ *
3785
+ * Handles:
3786
+ * - Single file: parseAgentFile("path/to/alice.yaml")
3787
+ * - Directory: discoverAgents("path/to/project") → scans .agents/*.yaml
3788
+ * - Validation: Zod schema + semantic checks (system XOR system_file)
3789
+ * - Resolution: system_file → reads content into system (relative to YAML dir)
3790
+ *
3791
+ * The name field is optional in YAML — defaults to filename (without .yaml).
3792
+ */
3793
+ /** Default directory for agent definitions (relative to project root) */
3794
+ const AGENTS_DIR = ".agents";
3795
+ /**
3796
+ * Parse an agent definition from a YAML file.
3797
+ *
3798
+ * Validates the schema, resolves system_file to inline content,
3799
+ * and infers name from filename if not specified.
3800
+ *
3801
+ * @throws Error if file doesn't exist, YAML is malformed, or validation fails.
3802
+ */
3803
+ function parseAgentFile(filePath) {
3804
+ if (!existsSync(filePath)) throw new Error(`Agent file not found: ${filePath}`);
3805
+ const data = parse(readFileSync(filePath, "utf-8"));
3806
+ if (!data || typeof data !== "object") throw new Error(`Invalid YAML in ${filePath}: expected an object`);
3807
+ const obj = data;
3808
+ if (!obj.name) obj.name = basename(filePath).replace(/\.ya?ml$/i, "");
3809
+ const result = AgentDefinitionSchema.safeParse(obj);
3810
+ if (!result.success) {
3811
+ const issues = result.error.issues.map((i) => ` ${i.path.join(".")}: ${i.message}`).join("\n");
3812
+ throw new Error(`Invalid agent definition in ${filePath}:\n${issues}`);
3813
+ }
3814
+ const def = result.data;
3815
+ if (def.prompt.system_file) {
3816
+ const promptPath = join(dirname(filePath), def.prompt.system_file);
3817
+ if (!existsSync(promptPath)) throw new Error(`system_file not found: ${def.prompt.system_file} (resolved: ${promptPath})`);
3818
+ const content = readFileSync(promptPath, "utf-8");
3819
+ return {
3820
+ ...def,
3821
+ prompt: { system: content }
3822
+ };
3823
+ }
3824
+ return def;
3825
+ }
3826
+ /**
3827
+ * Discover all agent YAML files in a project's .agents/ directory.
3828
+ * Returns parsed and validated definitions.
3829
+ *
3830
+ * Non-fatal: logs warnings for invalid files, skips them.
3831
+ *
3832
+ * @param projectDir - Project root directory
3833
+ * @param log - Optional warning logger (default: console.warn)
3834
+ * @returns Array of valid agent definitions
3835
+ */
3836
+ function discoverAgents(projectDir, log) {
3837
+ const agentsDir = join(projectDir, AGENTS_DIR);
3838
+ if (!existsSync(agentsDir)) return [];
3839
+ const warn = log ?? console.warn;
3840
+ const agents = [];
3841
+ let entries;
3842
+ try {
3843
+ entries = readdirSync(agentsDir);
3844
+ } catch {
3845
+ return [];
3846
+ }
3847
+ for (const entry of entries) {
3848
+ if (!entry.endsWith(".yaml") && !entry.endsWith(".yml")) continue;
3849
+ const filePath = join(agentsDir, entry);
3850
+ try {
3851
+ agents.push(parseAgentFile(filePath));
3852
+ } catch (err) {
3853
+ warn(`Skipping ${entry}: ${err instanceof Error ? err.message : String(err)}`);
3854
+ }
3855
+ }
3856
+ return agents;
3857
+ }
3858
+ /**
3859
+ * Serialize an agent definition to YAML string.
3860
+ * Used by CLI `agent create` to write .agents/<name>.yaml.
3861
+ */
3862
+ function serializeAgent(def) {
3863
+ const obj = {
3864
+ name: def.name,
3865
+ model: def.model
3866
+ };
3867
+ if (def.backend) obj.backend = def.backend;
3868
+ if (def.provider) obj.provider = def.provider;
3869
+ obj.prompt = def.prompt;
3870
+ if (def.soul) obj.soul = def.soul;
3871
+ if (def.context) obj.context = def.context;
3872
+ if (def.max_tokens) obj.max_tokens = def.max_tokens;
3873
+ if (def.max_steps) obj.max_steps = def.max_steps;
3874
+ if (def.schedule) obj.schedule = def.schedule;
3875
+ return stringify(obj, { lineWidth: 120 });
3876
+ }
3877
+
3878
+ //#endregion
3879
+ //#region src/agent/agent-registry.ts
3880
+ /**
3881
+ * AgentRegistry — Loads and manages top-level agent definitions.
3882
+ *
3883
+ * Responsibilities:
3884
+ * - Discover agents from .agents/*.yaml
3885
+ * - Load definitions → create AgentHandles
3886
+ * - Register/unregister agents at runtime
3887
+ * - Ensure context directories exist
3888
+ * - Provide agent lookup by name
3889
+ *
3890
+ * Owned by the daemon. One registry per daemon process.
3891
+ */
3892
+ var AgentRegistry = class {
3893
+ /** Loaded agent handles, keyed by name */
3894
+ agents = /* @__PURE__ */ new Map();
3895
+ /** Project root directory */
3896
+ projectDir;
3897
+ /** Agents directory (.agents/) */
3898
+ agentsDir;
3899
+ constructor(projectDir) {
3900
+ this.projectDir = projectDir;
3901
+ this.agentsDir = join(projectDir, AGENTS_DIR);
3902
+ }
3903
+ /**
3904
+ * Load all agents from .agents/*.yaml.
3905
+ * Skips invalid files (logs warnings).
3906
+ * Creates context directories for each loaded agent.
3907
+ */
3908
+ loadFromDisk(log) {
3909
+ const defs = discoverAgents(this.projectDir, log);
3910
+ for (const def of defs) this.registerDefinition(def);
3911
+ }
3912
+ /**
3913
+ * Register an agent definition. Creates AgentHandle + ensures context dir.
3914
+ * Overwrites existing agent with same name (reload semantics).
3915
+ */
3916
+ registerDefinition(def) {
3917
+ const handle = new AgentHandle(def, this.resolveContextDir(def));
3918
+ handle.ensureContextDir();
3919
+ this.agents.set(def.name, handle);
3920
+ return handle;
3921
+ }
3922
+ /**
3923
+ * Create a new agent: write YAML file + register.
3924
+ * @throws Error if agent already exists on disk.
3925
+ */
3926
+ create(def) {
3927
+ const yamlPath = this.agentYamlPath(def.name);
3928
+ if (existsSync(yamlPath)) throw new Error(`Agent file already exists: ${yamlPath}`);
3929
+ mkdirSync(this.agentsDir, { recursive: true });
3930
+ writeFileSync(yamlPath, serializeAgent(def));
3931
+ return this.registerDefinition(def);
3932
+ }
3933
+ /**
3934
+ * Delete an agent: remove YAML file + context directory + unregister.
3935
+ * @returns true if agent existed and was deleted.
3936
+ */
3937
+ delete(name) {
3938
+ const handle = this.agents.get(name);
3939
+ if (!handle) return false;
3940
+ this.agents.delete(name);
3941
+ const yamlPath = this.agentYamlPath(name);
3942
+ if (existsSync(yamlPath)) try {
3943
+ unlinkSync(yamlPath);
3944
+ } catch {}
3945
+ if (existsSync(handle.contextDir)) try {
3946
+ rmSync(handle.contextDir, {
3947
+ recursive: true,
3948
+ force: true
3949
+ });
3950
+ } catch {}
3951
+ return true;
3952
+ }
3953
+ /** Get agent handle by name */
3954
+ get(name) {
3955
+ return this.agents.get(name);
3956
+ }
3957
+ /** Check if agent exists */
3958
+ has(name) {
3959
+ return this.agents.has(name);
3960
+ }
3961
+ /** List all registered agent handles */
3962
+ list() {
3963
+ return [...this.agents.values()];
3964
+ }
3965
+ /** Number of registered agents */
3966
+ get size() {
3967
+ return this.agents.size;
3968
+ }
3969
+ /** Resolve agent's context directory (absolute path) */
3970
+ resolveContextDir(def) {
3971
+ if (def.context?.dir) return join(this.projectDir, def.context.dir);
3972
+ return join(this.agentsDir, def.name);
3973
+ }
3974
+ /** Path to agent's YAML file */
3975
+ agentYamlPath(name) {
3976
+ return join(this.agentsDir, `${name}.yaml`);
3977
+ }
3978
+ };
3979
+
3544
3980
  //#endregion
3545
3981
  //#region src/cli/commands/agent.ts
3546
3982
  var agent_exports = /* @__PURE__ */ __exportAll({
@@ -3655,7 +4091,7 @@ Examples:
3655
4091
  return;
3656
4092
  }
3657
4093
  for (const a of agents) {
3658
- const wf = a.tag === "main" ? `@${a.workflow}` : `@${a.workflow}:${a.tag}`;
4094
+ const wf = a.workflow ? a.tag === "main" ? `@${a.workflow}` : `@${a.workflow}:${a.tag}` : "";
3659
4095
  const info = a.model || a.state || "";
3660
4096
  console.log(`${a.name.padEnd(12)} ${info.padEnd(30)} ${wf}`);
3661
4097
  }
@@ -3753,6 +4189,121 @@ Examples:
3753
4189
  process.exit(1);
3754
4190
  } else console.log(res.content ?? JSON.stringify(res));
3755
4191
  });
4192
+ const agentCmd = program.command("agent").description("Manage persistent agent definitions (.agents/*.yaml)");
4193
+ agentCmd.command("create <name>").description("Create a persistent agent definition").option("-m, --model <model>", `Model (default: ${getDefaultModel()})`).addOption(new Option("-b, --backend <type>", "Backend type").choices([
4194
+ "sdk",
4195
+ "claude",
4196
+ "codex",
4197
+ "cursor",
4198
+ "opencode",
4199
+ "mock"
4200
+ ]).default(void 0)).option("-s, --system <prompt>", "System prompt").option("-f, --system-file <file>", "Read system prompt from file").option("--role <role>", "Soul: agent role").option("--expertise <items>", "Soul: expertise (comma-separated)").option("--style <style>", "Soul: communication style").option("--dir <path>", "Project directory", ".").option("--json", "Output as JSON").addHelpText("after", `
4201
+ Creates .agents/<name>.yaml and context directory (.agents/<name>/).
4202
+
4203
+ Examples:
4204
+ $ agent-worker agent create alice -m anthropic/claude-sonnet-4-5 -s "You are a code reviewer."
4205
+ $ agent-worker agent create bob --role developer --expertise "typescript,testing"
4206
+ $ agent-worker agent create coder -f ./prompts/coder.md
4207
+ `).action(async (name, options) => {
4208
+ const registry = new AgentRegistry(resolve(options.dir));
4209
+ let system = options.system ?? "You are a helpful assistant.";
4210
+ if (options.systemFile) system = readFileSync(options.systemFile, "utf-8");
4211
+ const def = {
4212
+ name,
4213
+ model: options.model || getDefaultModel(),
4214
+ prompt: { system }
4215
+ };
4216
+ if (options.backend) def.backend = options.backend;
4217
+ if (options.role || options.expertise || options.style) {
4218
+ def.soul = {};
4219
+ if (options.role) def.soul.role = options.role;
4220
+ if (options.expertise) def.soul.expertise = options.expertise.split(",").map((s) => s.trim());
4221
+ if (options.style) def.soul.style = options.style;
4222
+ }
4223
+ try {
4224
+ const handle = registry.create(def);
4225
+ if (options.json) outputJson({
4226
+ name,
4227
+ model: def.model,
4228
+ contextDir: handle.contextDir
4229
+ });
4230
+ else {
4231
+ console.log(`Created: .agents/${name}.yaml`);
4232
+ console.log(`Context: ${handle.contextDir}`);
4233
+ }
4234
+ } catch (err) {
4235
+ const msg = err instanceof Error ? err.message : String(err);
4236
+ console.error(`Error: ${msg}`);
4237
+ process.exit(1);
4238
+ }
4239
+ });
4240
+ agentCmd.command("list").description("List persistent agent definitions").option("--dir <path>", "Project directory", ".").option("--json", "Output as JSON").action(async (options) => {
4241
+ const registry = new AgentRegistry(resolve(options.dir));
4242
+ registry.loadFromDisk();
4243
+ const agents = registry.list();
4244
+ if (options.json) {
4245
+ outputJson({ agents: agents.map((h) => ({
4246
+ name: h.name,
4247
+ model: h.definition.model,
4248
+ backend: h.definition.backend,
4249
+ soul: h.definition.soul,
4250
+ contextDir: h.contextDir
4251
+ })) });
4252
+ return;
4253
+ }
4254
+ if (agents.length === 0) {
4255
+ console.log("No agent definitions found in .agents/");
4256
+ return;
4257
+ }
4258
+ for (const h of agents) {
4259
+ const soul = h.definition.soul?.role ? ` (${h.definition.soul.role})` : "";
4260
+ console.log(`${h.name.padEnd(16)} ${h.definition.model}${soul}`);
4261
+ }
4262
+ });
4263
+ agentCmd.command("info <name>").description("Show agent definition details").option("--dir <path>", "Project directory", ".").option("--json", "Output as JSON").action(async (name, options) => {
4264
+ const registry = new AgentRegistry(resolve(options.dir));
4265
+ registry.loadFromDisk();
4266
+ const handle = registry.get(name);
4267
+ if (!handle) {
4268
+ console.error(`Agent not found: ${name}`);
4269
+ process.exit(1);
4270
+ }
4271
+ const def = handle.definition;
4272
+ if (options.json) {
4273
+ outputJson({
4274
+ ...def,
4275
+ contextDir: handle.contextDir
4276
+ });
4277
+ return;
4278
+ }
4279
+ console.log(`Name: ${def.name}`);
4280
+ console.log(`Model: ${def.model}`);
4281
+ if (def.backend) console.log(`Backend: ${def.backend}`);
4282
+ if (def.prompt.system) {
4283
+ const preview = def.prompt.system.length > 80 ? def.prompt.system.slice(0, 77) + "..." : def.prompt.system;
4284
+ console.log(`Prompt: ${preview}`);
4285
+ }
4286
+ if (def.soul) {
4287
+ if (def.soul.role) console.log(`Role: ${def.soul.role}`);
4288
+ if (def.soul.expertise) console.log(`Expert: ${def.soul.expertise.join(", ")}`);
4289
+ if (def.soul.style) console.log(`Style: ${def.soul.style}`);
4290
+ if (def.soul.principles) {
4291
+ console.log(`Principles:`);
4292
+ for (const p of def.soul.principles) console.log(` - ${p}`);
4293
+ }
4294
+ }
4295
+ console.log(`Context: ${handle.contextDir}`);
4296
+ });
4297
+ agentCmd.command("delete <name>").description("Delete agent definition and context").option("--dir <path>", "Project directory", ".").action(async (name, options) => {
4298
+ const registry = new AgentRegistry(resolve(options.dir));
4299
+ registry.loadFromDisk();
4300
+ if (!registry.has(name)) {
4301
+ console.error(`Agent not found: ${name}`);
4302
+ process.exit(1);
4303
+ }
4304
+ registry.delete(name);
4305
+ console.log(`Deleted: ${name}`);
4306
+ });
3756
4307
  }
3757
4308
 
3758
4309
  //#endregion
@@ -3882,7 +4433,7 @@ Note: Workflow name is inferred from YAML 'name' field or filename.
3882
4433
  Workflow-defined params (see 'params:' in YAML) are passed after '--'.
3883
4434
  Set GITHUB_TOKEN env var to access private repositories.
3884
4435
  `).action(async (file, options) => {
3885
- const { parseWorkflowFile, parseWorkflowParams, formatParamHelp, runWorkflowWithLoops } = await import("../workflow-Ctto0bJt.mjs");
4436
+ const { parseWorkflowFile, parseWorkflowParams, formatParamHelp, runWorkflowWithLoops } = await import("../workflow-LOZUlaDo.mjs");
3886
4437
  const tag = options.tag || DEFAULT_TAG;
3887
4438
  const parsedWorkflow = await parseWorkflowFile(file, { tag });
3888
4439
  const workflowName = parsedWorkflow.name;
@@ -3905,7 +4456,7 @@ Note: Workflow name is inferred from YAML 'name' field or filename.
3905
4456
  isCleaningUp = true;
3906
4457
  console.log("\nInterrupted, cleaning up...");
3907
4458
  if (loops) {
3908
- const { shutdownLoops } = await import("../workflow-Ctto0bJt.mjs");
4459
+ const { shutdownLoops } = await import("../workflow-LOZUlaDo.mjs");
3909
4460
  const { createSilentLogger } = await Promise.resolve().then(() => logger_exports);
3910
4461
  await shutdownLoops(loops, createSilentLogger());
3911
4462
  }
@@ -3980,7 +4531,7 @@ Workflow runs inside the daemon. Use ls/stop to manage:
3980
4531
  Note: Workflow name is inferred from YAML 'name' field or filename.
3981
4532
  Workflow-defined params (see 'params:' in YAML) are passed after '--'.
3982
4533
  `).action(async (file, options) => {
3983
- const { parseWorkflowFile, parseWorkflowParams, formatParamHelp } = await import("../workflow-Ctto0bJt.mjs");
4534
+ const { parseWorkflowFile, parseWorkflowParams, formatParamHelp } = await import("../workflow-LOZUlaDo.mjs");
3984
4535
  const { ensureDaemon } = await Promise.resolve().then(() => agent_exports);
3985
4536
  const tag = options.tag || DEFAULT_TAG;
3986
4537
  const parsedWorkflow = await parseWorkflowFile(file, { tag });
@@ -4209,7 +4760,7 @@ Examples:
4209
4760
  $ agent-worker doc read @review:pr-123 # Read specific workflow:tag document
4210
4761
  `).action(async (targetInput) => {
4211
4762
  const dir = await resolveDir(targetInput);
4212
- const { createFileContextProvider } = await import("../context-CdcZpO-0.mjs");
4763
+ const { createFileContextProvider } = await import("../context-CoRTddGx.mjs");
4213
4764
  const content = await createFileContextProvider(dir, []).readDocument();
4214
4765
  console.log(content || "(empty document)");
4215
4766
  });
@@ -4227,7 +4778,7 @@ Examples:
4227
4778
  process.exit(1);
4228
4779
  }
4229
4780
  const dir = await resolveDir(targetInput);
4230
- const { createFileContextProvider } = await import("../context-CdcZpO-0.mjs");
4781
+ const { createFileContextProvider } = await import("../context-CoRTddGx.mjs");
4231
4782
  await createFileContextProvider(dir, []).writeDocument(content);
4232
4783
  console.log("Document written");
4233
4784
  });
@@ -4245,7 +4796,7 @@ Examples:
4245
4796
  process.exit(1);
4246
4797
  }
4247
4798
  const dir = await resolveDir(targetInput);
4248
- const { createFileContextProvider } = await import("../context-CdcZpO-0.mjs");
4799
+ const { createFileContextProvider } = await import("../context-CoRTddGx.mjs");
4249
4800
  await createFileContextProvider(dir, []).appendDocument(content);
4250
4801
  console.log("Content appended");
4251
4802
  });
@@ -4259,7 +4810,7 @@ async function resolveDir(targetInput) {
4259
4810
 
4260
4811
  //#endregion
4261
4812
  //#region package.json
4262
- var version = "0.16.0";
4813
+ var version = "0.17.0";
4263
4814
 
4264
4815
  //#endregion
4265
4816
  //#region src/cli/index.ts
@@ -4289,4 +4840,4 @@ registerDocCommands(program);
4289
4840
  program.parse();
4290
4841
 
4291
4842
  //#endregion
4292
- export { MENTION_PATTERN as A, formatProposal as B, DefaultDocumentStore as C, MemoryStorage as D, FileStorage as E, createResourceRef as F, getAgentId as G, createLogTool as H, extractMentions as I, EventLog as K, generateResourceId as L, RESOURCE_PREFIX$1 as M, RESOURCE_SCHEME as N, ContextProviderImpl as O, calculatePriority as P, shouldUseResource as R, DefaultResourceStore as S, DefaultChannelStore as T, formatInbox$1 as U, formatProposalList as V, formatToolParams as W, FileContextProvider as _, getBackendByType as a, resolveContextDir as b, createAgentLoop as c, generateWorkflowMCPConfig as d, writeBackendMcpConfig as f, runWithHttp as g, LOOP_DEFAULTS as h, createSilentLogger as i, MESSAGE_LENGTH_THRESHOLD as j, CONTEXT_DEFAULTS as k, runSdkAgent as l, formatInbox as m, createWiredLoop as n, getBackendForModel as o, buildAgentPrompt as p, createChannelLogger as r, checkWorkflowIdle as s, createMinimalRuntime as t, runMockAgent as u, createFileContextProvider as v, DefaultInboxStore as w, DefaultStatusStore as x, getDefaultContextDir as y, createContextMCPServer as z };
4843
+ export { formatInbox$1 as $, resolveContextDir as A, MENTION_PATTERN as B, retrySection as C, FileContextProvider as D, runWithHttp as E, DefaultChannelStore as F, createResourceRef as G, RESOURCE_PREFIX$1 as H, FileStorage as I, shouldUseResource as J, extractMentions as K, MemoryStorage as L, DefaultResourceStore as M, DefaultDocumentStore as N, createFileContextProvider as O, DefaultInboxStore as P, createLogTool as Q, ContextProviderImpl as R, projectSection as S, LOOP_DEFAULTS as T, RESOURCE_SCHEME as U, MESSAGE_LENGTH_THRESHOLD as V, calculatePriority as W, formatProposal as X, createContextMCPServer as Y, formatProposalList as Z, documentSection as _, getBackendByType as a, inboxSection as b, createAgentLoop as c, generateWorkflowMCPConfig as d, formatToolParams as et, writeBackendMcpConfig as f, buildAgentPrompt as g, assemblePrompt as h, createSilentLogger as i, DefaultStatusStore as j, getDefaultContextDir as k, runSdkAgent as l, activitySection as m, createWiredLoop as n, EventLog as nt, getBackendForModel as o, DEFAULT_SECTIONS as p, generateResourceId as q, createChannelLogger as r, checkWorkflowIdle as s, createMinimalRuntime as t, getAgentId as tt, runMockAgent as u, exitSection as v, workflowSection as w, instructionsSection as x, formatInbox as y, CONTEXT_DEFAULTS as z };
@@ -0,0 +1,4 @@
1
+ import { $ as formatInbox, A as resolveContextDir, B as MENTION_PATTERN, D as FileContextProvider, F as DefaultChannelStore, G as createResourceRef, H as RESOURCE_PREFIX, I as FileStorage, J as shouldUseResource, K as extractMentions, L as MemoryStorage, M as DefaultResourceStore, N as DefaultDocumentStore, O as createFileContextProvider, P as DefaultInboxStore, Q as createLogTool, R as ContextProviderImpl, U as RESOURCE_SCHEME, V as MESSAGE_LENGTH_THRESHOLD, W as calculatePriority, X as formatProposal, Y as createContextMCPServer, Z as formatProposalList, et as formatToolParams, j as DefaultStatusStore, k as getDefaultContextDir, nt as EventLog, q as generateResourceId, tt as getAgentId, z as CONTEXT_DEFAULTS } from "./cli/index.mjs";
2
+ import { n as createMemoryContextProvider, t as MemoryContextProvider } from "./memory-provider-Z9D8NdwS.mjs";
3
+
4
+ export { createFileContextProvider };
package/dist/index.d.mts CHANGED
@@ -1,5 +1,6 @@
1
1
  import { LanguageModel } from "ai";
2
2
  import { BashToolkit, CreateBashToolOptions, createBashTool } from "bash-tool";
3
+ import { z } from "zod/v4";
3
4
 
4
5
  //#region src/backends/model-maps.d.ts
5
6
  /**
@@ -1,4 +1,4 @@
1
- import { C as DefaultDocumentStore, D as MemoryStorage, O as ContextProviderImpl, S as DefaultResourceStore, T as DefaultChannelStore, w as DefaultInboxStore, x as DefaultStatusStore } from "./cli/index.mjs";
1
+ import { F as DefaultChannelStore, L as MemoryStorage, M as DefaultResourceStore, N as DefaultDocumentStore, P as DefaultInboxStore, R as ContextProviderImpl, j as DefaultStatusStore } from "./cli/index.mjs";
2
2
 
3
3
  //#region src/workflow/context/memory-provider.ts
4
4
  /**
@@ -1,7 +1,7 @@
1
1
  import "./backends-D7DT0uox.mjs";
2
2
  import "./create-tool-gcUuI1FD.mjs";
3
- import { K as EventLog, _ as FileContextProvider, g as runWithHttp, i as createSilentLogger, n as createWiredLoop, r as createChannelLogger, s as checkWorkflowIdle, v as createFileContextProvider, z as createContextMCPServer } from "./cli/index.mjs";
4
- import { n as createMemoryContextProvider } from "./memory-provider-ZLOKyCxA.mjs";
3
+ import { D as FileContextProvider, E as runWithHttp, O as createFileContextProvider, Y as createContextMCPServer, i as createSilentLogger, n as createWiredLoop, nt as EventLog, r as createChannelLogger, s as checkWorkflowIdle } from "./cli/index.mjs";
4
+ import { n as createMemoryContextProvider } from "./memory-provider-Z9D8NdwS.mjs";
5
5
  import { existsSync, mkdirSync } from "node:fs";
6
6
  import { join } from "node:path";
7
7
  import { tmpdir } from "node:os";
@@ -1,8 +1,8 @@
1
1
  import "./backends-D7DT0uox.mjs";
2
2
  import "./create-tool-gcUuI1FD.mjs";
3
- import { a as getBackendByType, b as resolveContextDir, c as createAgentLoop, d as generateWorkflowMCPConfig, f as writeBackendMcpConfig, h as LOOP_DEFAULTS, i as createSilentLogger, k as CONTEXT_DEFAULTS, l as runSdkAgent, m as formatInbox, n as createWiredLoop, o as getBackendForModel, p as buildAgentPrompt, r as createChannelLogger, s as checkWorkflowIdle, t as createMinimalRuntime, u as runMockAgent } from "./cli/index.mjs";
4
- import "./memory-provider-ZLOKyCxA.mjs";
5
- import { createWorkflowProvider, initWorkflow, n as interpolate, runWorkflowWithLoops, shutdownLoops, t as createContext } from "./runner-IkYhcbio.mjs";
3
+ import { A as resolveContextDir, C as retrySection, S as projectSection, T as LOOP_DEFAULTS, _ as documentSection, a as getBackendByType, b as inboxSection, c as createAgentLoop, d as generateWorkflowMCPConfig, f as writeBackendMcpConfig, g as buildAgentPrompt, h as assemblePrompt, i as createSilentLogger, l as runSdkAgent, m as activitySection, n as createWiredLoop, o as getBackendForModel, p as DEFAULT_SECTIONS, r as createChannelLogger, s as checkWorkflowIdle, t as createMinimalRuntime, u as runMockAgent, v as exitSection, w as workflowSection, x as instructionsSection, y as formatInbox, z as CONTEXT_DEFAULTS } from "./cli/index.mjs";
4
+ import "./memory-provider-Z9D8NdwS.mjs";
5
+ import { createWorkflowProvider, initWorkflow, n as interpolate, runWorkflowWithLoops, shutdownLoops, t as createContext } from "./runner-BmT0Y8MD.mjs";
6
6
  import { existsSync, mkdirSync, readFileSync, rmSync } from "node:fs";
7
7
  import { basename, dirname, join, resolve } from "node:path";
8
8
  import { parse } from "yaml";
@@ -10,6 +10,13 @@ import { homedir } from "node:os";
10
10
  import { execFileSync } from "node:child_process";
11
11
  import { parseArgs } from "node:util";
12
12
 
13
+ //#region src/workflow/types.ts
14
+ /** Type guard: is this agent entry a reference to a global agent? */
15
+ function isRefAgentEntry(entry) {
16
+ return "ref" in entry && typeof entry.ref === "string";
17
+ }
18
+
19
+ //#endregion
13
20
  //#region src/workflow/source.ts
14
21
  /**
15
22
  * Workflow source resolver — supports local files and remote GitHub references.
@@ -263,7 +270,8 @@ async function parseWorkflowFile(filePath, options) {
263
270
  }
264
271
  const name = raw.name || source.inferredName;
265
272
  const agents = {};
266
- for (const [agentName, agentDef] of Object.entries(raw.agents)) agents[agentName] = await resolveAgent(agentDef, source.readRelativeFile);
273
+ for (const [agentName, entry] of Object.entries(raw.agents)) if (isRefAgentEntry(entry)) agents[agentName] = resolveRefAgent(entry, options?.agentRegistry);
274
+ else agents[agentName] = await resolveInlineAgent(entry, source.readRelativeFile);
267
275
  const context = resolveContext(raw.context, contextBaseDir, name, workflow, tag);
268
276
  return {
269
277
  name,
@@ -315,14 +323,14 @@ function resolveContext(config, workflowDir, workflowName, workflow, tag) {
315
323
  };
316
324
  }
317
325
  /**
318
- * Resolve agent definition (load system prompt from file if needed).
326
+ * Resolve an inline agent definition (load system prompt from file if needed).
319
327
  *
320
328
  * Uses a `readRelativeFile` function to abstract local vs remote file access.
321
329
  * Also transforms `wakeup` and `wakeup_prompt` fields into a `ScheduleConfig`
322
330
  * object, which is the format expected by the daemon and loop layers
323
331
  * for setting up periodic wakeup timers.
324
332
  */
325
- async function resolveAgent(agent, readRelativeFile) {
333
+ async function resolveInlineAgent(agent, readRelativeFile) {
326
334
  let resolvedSystemPrompt = agent.system_prompt;
327
335
  if (resolvedSystemPrompt?.endsWith(".txt") || resolvedSystemPrompt?.endsWith(".md")) {
328
336
  const content = await readRelativeFile(resolvedSystemPrompt);
@@ -336,7 +344,34 @@ async function resolveAgent(agent, readRelativeFile) {
336
344
  return {
337
345
  ...agent,
338
346
  resolvedSystemPrompt,
339
- schedule
347
+ schedule,
348
+ isRef: false
349
+ };
350
+ }
351
+ /**
352
+ * Resolve a ref agent entry — load from AgentRegistry, map to WorkflowAgentDef,
353
+ * apply workflow overrides (prompt.append, max_tokens, max_steps).
354
+ */
355
+ function resolveRefAgent(entry, registry) {
356
+ if (!registry) throw new Error(`Agent ref "${entry.ref}" requires an AgentRegistry. Pass agentRegistry in ParseOptions.`);
357
+ const handle = registry.get(entry.ref);
358
+ if (!handle) throw new Error(`Agent ref "${entry.ref}" not found in registry. Available agents: ${registry.list().map((h) => h.definition.name).join(", ") || "(none)"}`);
359
+ const def = handle.definition;
360
+ const basePrompt = def.prompt.system ?? "";
361
+ let resolvedSystemPrompt = basePrompt;
362
+ if (entry.prompt?.append) resolvedSystemPrompt = basePrompt ? `${basePrompt}\n\n${entry.prompt.append}` : entry.prompt.append;
363
+ const backend = def.backend === "sdk" ? "default" : def.backend;
364
+ return {
365
+ model: def.model,
366
+ backend,
367
+ provider: def.provider,
368
+ system_prompt: basePrompt,
369
+ max_tokens: entry.max_tokens ?? def.max_tokens,
370
+ max_steps: entry.max_steps ?? def.max_steps,
371
+ schedule: def.schedule,
372
+ resolvedSystemPrompt,
373
+ handle,
374
+ isRef: true
340
375
  };
341
376
  }
342
377
  /**
@@ -538,6 +573,51 @@ function validateAgent(name, agent, errors) {
538
573
  return;
539
574
  }
540
575
  const a = agent;
576
+ if (typeof a.ref === "string") validateRefAgent(path, a, errors);
577
+ else validateInlineAgent(path, a, errors);
578
+ }
579
+ function validateRefAgent(path, a, errors) {
580
+ if (!a.ref.length) errors.push({
581
+ path: `${path}.ref`,
582
+ message: "Field \"ref\" must be a non-empty string"
583
+ });
584
+ if (a.prompt !== void 0) if (typeof a.prompt !== "object" || a.prompt === null) errors.push({
585
+ path: `${path}.prompt`,
586
+ message: "Field \"prompt\" for ref agents must be an object with optional \"append\""
587
+ });
588
+ else {
589
+ const p = a.prompt;
590
+ if (p.append !== void 0 && typeof p.append !== "string") errors.push({
591
+ path: `${path}.prompt.append`,
592
+ message: "Field \"prompt.append\" must be a string"
593
+ });
594
+ }
595
+ if (a.system_prompt !== void 0) errors.push({
596
+ path: `${path}.system_prompt`,
597
+ message: "Field \"system_prompt\" cannot be used with ref agents — use \"prompt.append\" instead"
598
+ });
599
+ for (const field of [
600
+ "model",
601
+ "backend",
602
+ "provider",
603
+ "tools",
604
+ "wakeup",
605
+ "wakeup_prompt",
606
+ "timeout"
607
+ ]) if (a[field] !== void 0) errors.push({
608
+ path: `${path}.${field}`,
609
+ message: `Field "${field}" cannot be used with ref agents — it comes from the agent definition`
610
+ });
611
+ if (a.max_tokens !== void 0 && typeof a.max_tokens !== "number") errors.push({
612
+ path: `${path}.max_tokens`,
613
+ message: "Field \"max_tokens\" must be a number"
614
+ });
615
+ if (a.max_steps !== void 0 && typeof a.max_steps !== "number") errors.push({
616
+ path: `${path}.max_steps`,
617
+ message: "Field \"max_steps\" must be a number"
618
+ });
619
+ }
620
+ function validateInlineAgent(path, a, errors) {
541
621
  const backend = typeof a.backend === "string" ? a.backend : "default";
542
622
  if (a.model !== void 0 && typeof a.model !== "string") errors.push({
543
623
  path: `${path}.model`,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agent-worker",
3
- "version": "0.16.0",
3
+ "version": "0.17.0",
4
4
  "description": "SDK and CLI for creating and testing agent workers with Vercel AI SDK",
5
5
  "type": "module",
6
6
  "main": "./dist/index.mjs",
@@ -29,6 +29,7 @@
29
29
  "format": "oxfmt src",
30
30
  "format:check": "oxfmt src --check",
31
31
  "typecheck": "tsgo",
32
+ "gen:schema": "bun scripts/gen-workflow-ref.ts > docs/workflow/SCHEMA.md",
32
33
  "prepublishOnly": "bun run build"
33
34
  },
34
35
  "dependencies": {
@@ -1,4 +0,0 @@
1
- import { A as MENTION_PATTERN, B as formatProposal, C as DefaultDocumentStore, D as MemoryStorage, E as FileStorage, F as createResourceRef, G as getAgentId, H as createLogTool, I as extractMentions, K as EventLog, L as generateResourceId, M as RESOURCE_PREFIX, N as RESOURCE_SCHEME, O as ContextProviderImpl, P as calculatePriority, R as shouldUseResource, S as DefaultResourceStore, T as DefaultChannelStore, U as formatInbox, V as formatProposalList, W as formatToolParams, _ as FileContextProvider, b as resolveContextDir, j as MESSAGE_LENGTH_THRESHOLD, k as CONTEXT_DEFAULTS, v as createFileContextProvider, w as DefaultInboxStore, x as DefaultStatusStore, y as getDefaultContextDir, z as createContextMCPServer } from "./cli/index.mjs";
2
- import { n as createMemoryContextProvider, t as MemoryContextProvider } from "./memory-provider-ZLOKyCxA.mjs";
3
-
4
- export { createFileContextProvider };