@agentic-surfaces/core 0.1.8 → 0.1.10

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.
@@ -0,0 +1,26 @@
1
+ export interface AgentCommand {
2
+ name: string;
3
+ description?: string;
4
+ }
5
+ export interface AgentDefinition {
6
+ name: string;
7
+ model?: string;
8
+ effort?: "low" | "medium" | "high" | "xhigh" | "max";
9
+ commands?: AgentCommand[];
10
+ fallback?: string;
11
+ mcpServers?: Array<{
12
+ name: string;
13
+ url?: string;
14
+ command?: string;
15
+ args?: string[];
16
+ }>;
17
+ instructions: string;
18
+ }
19
+ /**
20
+ * Parse one agent `.md` file into an AgentDefinition.
21
+ *
22
+ * @param content Raw file contents (optional YAML frontmatter + markdown body).
23
+ * @param defaultName Used as `name` when the frontmatter omits it (e.g. the filename).
24
+ * @throws if the frontmatter is present but invalid YAML or fails validation.
25
+ */
26
+ export declare function parseAgentFile(content: string, defaultName: string): AgentDefinition;
package/dist/agents.js ADDED
@@ -0,0 +1,73 @@
1
+ import { z } from "zod";
2
+ import { parse as parseYaml } from "yaml";
3
+ // A reusable agent definition loaded from agents/<name>.md — YAML frontmatter
4
+ // (config) + a markdown body (instructions). Decision agents declare a `commands`
5
+ // allowlist (+ fallback); worker agents omit it and just do a task on their model.
6
+ const McpServerRefSchema = z.union([
7
+ z.object({ name: z.string(), url: z.string() }),
8
+ z.object({ name: z.string(), command: z.string(), args: z.array(z.string()).optional() }),
9
+ ]);
10
+ const AgentCommandSchema = z.object({
11
+ name: z.string().min(1),
12
+ description: z.string().optional(),
13
+ });
14
+ const AgentFrontmatterSchema = z
15
+ .object({
16
+ name: z.string().min(1).optional(),
17
+ model: z.string().optional(),
18
+ effort: z.enum(["low", "medium", "high", "xhigh", "max"]).optional(),
19
+ commands: z.array(AgentCommandSchema).min(1).optional(),
20
+ fallback: z.string().optional(),
21
+ mcpServers: z.array(McpServerRefSchema).optional(),
22
+ })
23
+ .superRefine((v, ctx) => {
24
+ if (v.commands) {
25
+ const names = v.commands.map((c) => c.name);
26
+ if (new Set(names).size !== names.length) {
27
+ ctx.addIssue({ code: "custom", message: "command names must be unique", path: ["commands"] });
28
+ }
29
+ if (v.fallback && !names.includes(v.fallback)) {
30
+ ctx.addIssue({ code: "custom", message: `fallback "${v.fallback}" is not one of the commands`, path: ["fallback"] });
31
+ }
32
+ }
33
+ else if (v.fallback) {
34
+ ctx.addIssue({ code: "custom", message: "fallback requires a commands list", path: ["fallback"] });
35
+ }
36
+ });
37
+ const FRONTMATTER = /^---\r?\n([\s\S]*?)\r?\n---\s*\r?\n?([\s\S]*)$/;
38
+ /**
39
+ * Parse one agent `.md` file into an AgentDefinition.
40
+ *
41
+ * @param content Raw file contents (optional YAML frontmatter + markdown body).
42
+ * @param defaultName Used as `name` when the frontmatter omits it (e.g. the filename).
43
+ * @throws if the frontmatter is present but invalid YAML or fails validation.
44
+ */
45
+ export function parseAgentFile(content, defaultName) {
46
+ let rawFm = {};
47
+ let body = content;
48
+ const m = content.match(FRONTMATTER);
49
+ if (m) {
50
+ try {
51
+ rawFm = parseYaml(m[1]) ?? {};
52
+ }
53
+ catch (err) {
54
+ throw new Error(`agent "${defaultName}": invalid frontmatter YAML: ${err instanceof Error ? err.message : String(err)}`);
55
+ }
56
+ body = m[2] ?? "";
57
+ }
58
+ const result = AgentFrontmatterSchema.safeParse(rawFm);
59
+ if (!result.success) {
60
+ const issues = result.error.issues.map((i) => ` ${i.path.join(".")}: ${i.message}`).join("\n");
61
+ throw new Error(`agent "${defaultName}": invalid definition:\n${issues}`);
62
+ }
63
+ const fm = result.data;
64
+ return {
65
+ name: fm.name ?? defaultName,
66
+ model: fm.model,
67
+ effort: fm.effort,
68
+ commands: fm.commands,
69
+ fallback: fm.fallback,
70
+ mcpServers: fm.mcpServers,
71
+ instructions: body.trim(),
72
+ };
73
+ }
@@ -2,20 +2,64 @@ export const agentHandler = {
2
2
  type: "agent.run",
3
3
  async execute(node, ctx) {
4
4
  const cfg = node.config;
5
+ // Resolve a named agent file, if referenced.
6
+ let instructions = cfg.prompt ?? "";
7
+ let model = cfg.model;
8
+ let effort = cfg.effort;
9
+ let mcpServers = cfg.mcpServers;
10
+ let decisionSchema = cfg.decision;
11
+ let commands;
12
+ let fallback;
13
+ if (cfg.agent) {
14
+ const def = ctx.services.agents?.get(cfg.agent);
15
+ if (!def)
16
+ throw new Error(`unknown agent: ${cfg.agent}`);
17
+ // Agent file body is the prompt; a node-level prompt is appended as extra guidance.
18
+ instructions = cfg.prompt ? `${def.instructions}\n\n${cfg.prompt}` : def.instructions;
19
+ model = cfg.model ?? def.model;
20
+ effort = cfg.effort ?? def.effort;
21
+ mcpServers = cfg.mcpServers ?? def.mcpServers;
22
+ commands = def.commands;
23
+ fallback = def.fallback;
24
+ // A decision agent's commands generate the decision schema (the allowlist).
25
+ if (commands?.length) {
26
+ decisionSchema = {
27
+ type: "object",
28
+ properties: { command: { enum: commands.map((c) => c.name) } },
29
+ required: ["command"],
30
+ };
31
+ }
32
+ }
33
+ if (!cfg.agent && !cfg.prompt) {
34
+ throw new Error("agent.run requires either `agent` or `prompt`");
35
+ }
5
36
  const defaults = ctx.services.agentDefaults ?? {};
6
- const model = cfg.model ?? defaults.model;
7
- const effort = cfg.effort ?? defaults.effort; // per-node wins, else project default
8
- const mcpServers = cfg.mcpServers ?? defaults.mcpServers;
37
+ model = model ?? defaults.model;
38
+ effort = effort ?? defaults.effort;
39
+ mcpServers = mcpServers ?? defaults.mcpServers;
9
40
  const result = await ctx.services.agent.run({
10
- prompt: cfg.prompt,
41
+ prompt: instructions,
11
42
  model,
12
43
  effort,
13
44
  mcpServers,
14
45
  context: Object.fromEntries(ctx.outputs),
15
- decisionSchema: cfg.decision,
46
+ decisionSchema,
16
47
  });
17
- // With a decision schema, surface the decision's fields at the top level so
18
- // jsonata (e.g. task.branch `interpret.action`) can route on them; keep .text.
48
+ // Decision agent: guarantee the chosen command is on the allowlist (else use
49
+ // the fallback), and surface it at the top level so task.branch can route.
50
+ if (commands?.length) {
51
+ const names = new Set(commands.map((c) => c.name));
52
+ const raw = result.data?.command;
53
+ let command = typeof raw === "string" ? raw : "";
54
+ if (!names.has(command)) {
55
+ if (fallback)
56
+ command = fallback;
57
+ else
58
+ throw new Error(`agent ${cfg.agent} returned an off-allowlist command: ${String(raw)}`);
59
+ }
60
+ return { output: { command, text: result.text } };
61
+ }
62
+ // Inline decision schema: surface its fields at the top level (0.1.7 behaviour).
19
63
  if (cfg.decision && result.data && typeof result.data === "object" && !Array.isArray(result.data)) {
20
64
  return { output: { ...result.data, text: result.text } };
21
65
  }
package/dist/index.d.ts CHANGED
@@ -7,5 +7,6 @@ export * from "./cache.js";
7
7
  export * from "./observer.js";
8
8
  export * from "./secrets.js";
9
9
  export * from "./project-config.js";
10
+ export * from "./agents.js";
10
11
  export * from "./handlers/foreach.js";
11
12
  export * from "./handlers/run-workflow.js";
package/dist/index.js CHANGED
@@ -7,5 +7,6 @@ export * from "./cache.js";
7
7
  export * from "./observer.js";
8
8
  export * from "./secrets.js";
9
9
  export * from "./project-config.js";
10
+ export * from "./agents.js";
10
11
  export * from "./handlers/foreach.js";
11
12
  export * from "./handlers/run-workflow.js";
package/dist/types.d.ts CHANGED
@@ -1,4 +1,5 @@
1
1
  import type { AgentRunner, McpServerRef } from "@agentic-surfaces/agent";
2
+ import type { AgentDefinition } from "./agents.js";
2
3
  export type { AgentRunner, McpServerRef };
3
4
  export type NodeType = "trigger.cron" | "trigger.webhook" | "trigger.command" | "task.http" | "task.transform" | "task.branch" | "task.run-workflow" | "task.foreach" | "agent.run";
4
5
  export interface RetryPolicy {
@@ -46,6 +47,7 @@ export interface Services {
46
47
  mcpServers?: McpServerRef[];
47
48
  };
48
49
  runWorkflow?: (name: string, payload: unknown) => Promise<Map<string, unknown>>;
50
+ agents?: Map<string, AgentDefinition>;
49
51
  }
50
52
  export interface RunContext {
51
53
  workflow: Workflow;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@agentic-surfaces/core",
3
- "version": "0.1.8",
3
+ "version": "0.1.10",
4
4
  "type": "module",
5
5
  "license": "MIT",
6
6
  "publishConfig": {
@@ -23,7 +23,7 @@
23
23
  "jsonata": "^2.2.1",
24
24
  "yaml": "^2.9.0",
25
25
  "zod": "^4.4.3",
26
- "@agentic-surfaces/agent": "0.1.8"
26
+ "@agentic-surfaces/agent": "0.1.10"
27
27
  },
28
28
  "devDependencies": {
29
29
  "@types/better-sqlite3": "^7.6.13",