@vextlabs/theron-agent-sdk 0.3.0 → 0.3.1
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.
- package/CHANGELOG.md +16 -0
- package/dist/adapters/theron.cjs +13 -2
- package/dist/adapters/theron.js +13 -2
- package/dist/agent/index.cjs +111 -2
- package/dist/agent/index.d.cts +43 -2
- package/dist/agent/index.d.ts +43 -2
- package/dist/agent/index.js +108 -3
- package/dist/council/index.cjs +4 -0
- package/dist/council/index.d.cts +25 -1
- package/dist/council/index.d.ts +25 -1
- package/dist/council/index.js +4 -1
- package/dist/index.cjs +490 -65
- package/dist/index.d.cts +64 -5
- package/dist/index.d.ts +64 -5
- package/dist/index.js +479 -66
- package/dist/loop/index.cjs +2 -1
- package/dist/loop/index.d.cts +3 -0
- package/dist/loop/index.d.ts +3 -0
- package/dist/loop/index.js +2 -1
- package/dist/mcp/index.cjs +27 -10
- package/dist/mcp/index.d.cts +2 -1
- package/dist/mcp/index.d.ts +2 -1
- package/dist/mcp/index.js +27 -10
- package/dist/runtime/index.cjs +145 -33
- package/dist/runtime/index.d.cts +78 -3
- package/dist/runtime/index.d.ts +78 -3
- package/dist/runtime/index.js +144 -34
- package/dist/tools/index.cjs +45 -10
- package/dist/tools/index.js +45 -10
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,22 @@ This project follows [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
|
5
5
|
|
|
6
6
|
## [Unreleased]
|
|
7
7
|
|
|
8
|
+
## [0.3.1] - 2026-06-26
|
|
9
|
+
|
|
10
|
+
### Added
|
|
11
|
+
- **Sub-agent delegation actually executes.** A supervisor `Agent` with `sub_agents` now exposes one `delegate_to_<name>` tool per sub-agent (via `toolSchemas()`), and the `Runner` routes those calls back into `runner.run(subAgent, task)`, threading the abort signal — hierarchical multi-agent graphs work end-to-end instead of `sub_agents` being a silent no-op. New export `subAgentToolName`.
|
|
12
|
+
- **On-disk SKILL.md loader** — `parseMarkdownSkill` / `loadMarkdownSkills` / `loadAllMarkdownSkills` (+ `MarkdownSkill` type) load user/project skills from `~/.theron/skills/*.md` and `<project>/.theron/skills/*.md` (also `<name>/SKILL.md` dirs), with project-local overriding global. Mirrors the markdown-agent loader; consumed by both the CLI and VS Code surfaces.
|
|
13
|
+
- **`AbortSignal` cancellation** — `Runner.run`/`runCouncil` accept `{ signal }` (new `RunOptions`); the loop checks it each turn and emits an `aborted` event; `ModelAdapter.chat` gains an optional `signal` to forward to `fetch`.
|
|
14
|
+
- **Cost accounting** — `ModelAdapter.chat` may return `cost_usd`; the Runner sums it into `AgentResult.cost_usd`, so `costUsdAtLeast` and budget-stop now work.
|
|
15
|
+
- **`max_turns_exhausted` event** — emitted when the loop runs out of turns without a final answer (instead of silently returning `""`).
|
|
16
|
+
- **Council `claimExtractor`** — opt-in extractor (e.g. exported `sentenceClaimExtractor`) so the deterministic reconciler can ratify cross-specialist claims; default behavior unchanged.
|
|
17
|
+
- **`compactHistory({ summaryRole })`** — attach the summary under `user` instead of `system` for providers that reject a second system message.
|
|
18
|
+
|
|
19
|
+
### Changed
|
|
20
|
+
- **Parallel tool dispatch.** Multiple tool calls in a single turn now execute concurrently (`Promise.all`) with results appended in call order — turn latency drops from sum-of-tools to slowest-tool.
|
|
21
|
+
- **Robust Zod → JSON Schema.** The converter now handles unions, literals, records, nullables, defaults, enums, and `.describe()` annotations (and excludes default-valued fields from `required`) instead of silently emitting `{type:"string"}`.
|
|
22
|
+
- **MCP single-flight init.** Concurrent `listTools`/`callTool` callers share one in-flight `initialize` handshake (no duplicate sessions); a failed init is retryable.
|
|
23
|
+
|
|
8
24
|
## [0.3.0] - 2026-06-13
|
|
9
25
|
|
|
10
26
|
### Added
|
package/dist/adapters/theron.cjs
CHANGED
|
@@ -35,6 +35,7 @@ function theronAdapter(opts = {}) {
|
|
|
35
35
|
let inputTokens = 0;
|
|
36
36
|
let outputTokens = 0;
|
|
37
37
|
let buf = "";
|
|
38
|
+
const toolAcc = {};
|
|
38
39
|
for (; ; ) {
|
|
39
40
|
const { value, done } = await reader.read();
|
|
40
41
|
if (done) break;
|
|
@@ -47,11 +48,20 @@ function theronAdapter(opts = {}) {
|
|
|
47
48
|
if (!data || data === "[DONE]") continue;
|
|
48
49
|
try {
|
|
49
50
|
const json2 = JSON.parse(data);
|
|
50
|
-
const
|
|
51
|
+
const d = json2.choices?.[0]?.delta;
|
|
52
|
+
const delta = d?.content;
|
|
51
53
|
if (delta) {
|
|
52
54
|
onDelta(delta);
|
|
53
55
|
content += delta;
|
|
54
56
|
}
|
|
57
|
+
if (Array.isArray(d?.tool_calls)) {
|
|
58
|
+
for (const tc of d.tool_calls) {
|
|
59
|
+
const i = typeof tc.index === "number" ? tc.index : 0;
|
|
60
|
+
toolAcc[i] ??= { name: "", args: "" };
|
|
61
|
+
if (tc.function?.name) toolAcc[i].name = tc.function.name;
|
|
62
|
+
if (tc.function?.arguments) toolAcc[i].args += tc.function.arguments;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
55
65
|
if (json2.usage) {
|
|
56
66
|
inputTokens = json2.usage.prompt_tokens ?? inputTokens;
|
|
57
67
|
outputTokens = json2.usage.completion_tokens ?? outputTokens;
|
|
@@ -60,7 +70,8 @@ function theronAdapter(opts = {}) {
|
|
|
60
70
|
}
|
|
61
71
|
}
|
|
62
72
|
}
|
|
63
|
-
|
|
73
|
+
const tool_calls2 = Object.keys(toolAcc).length ? Object.values(toolAcc).map((t) => ({ name: t.name, input: safeJson(t.args) })) : void 0;
|
|
74
|
+
return { content, tool_calls: tool_calls2, tokens: { input: inputTokens, output: outputTokens } };
|
|
64
75
|
}
|
|
65
76
|
const json = await res.json();
|
|
66
77
|
const msg = json.choices?.[0]?.message ?? { content: "" };
|
package/dist/adapters/theron.js
CHANGED
|
@@ -33,6 +33,7 @@ function theronAdapter(opts = {}) {
|
|
|
33
33
|
let inputTokens = 0;
|
|
34
34
|
let outputTokens = 0;
|
|
35
35
|
let buf = "";
|
|
36
|
+
const toolAcc = {};
|
|
36
37
|
for (; ; ) {
|
|
37
38
|
const { value, done } = await reader.read();
|
|
38
39
|
if (done) break;
|
|
@@ -45,11 +46,20 @@ function theronAdapter(opts = {}) {
|
|
|
45
46
|
if (!data || data === "[DONE]") continue;
|
|
46
47
|
try {
|
|
47
48
|
const json2 = JSON.parse(data);
|
|
48
|
-
const
|
|
49
|
+
const d = json2.choices?.[0]?.delta;
|
|
50
|
+
const delta = d?.content;
|
|
49
51
|
if (delta) {
|
|
50
52
|
onDelta(delta);
|
|
51
53
|
content += delta;
|
|
52
54
|
}
|
|
55
|
+
if (Array.isArray(d?.tool_calls)) {
|
|
56
|
+
for (const tc of d.tool_calls) {
|
|
57
|
+
const i = typeof tc.index === "number" ? tc.index : 0;
|
|
58
|
+
toolAcc[i] ??= { name: "", args: "" };
|
|
59
|
+
if (tc.function?.name) toolAcc[i].name = tc.function.name;
|
|
60
|
+
if (tc.function?.arguments) toolAcc[i].args += tc.function.arguments;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
53
63
|
if (json2.usage) {
|
|
54
64
|
inputTokens = json2.usage.prompt_tokens ?? inputTokens;
|
|
55
65
|
outputTokens = json2.usage.completion_tokens ?? outputTokens;
|
|
@@ -58,7 +68,8 @@ function theronAdapter(opts = {}) {
|
|
|
58
68
|
}
|
|
59
69
|
}
|
|
60
70
|
}
|
|
61
|
-
|
|
71
|
+
const tool_calls2 = Object.keys(toolAcc).length ? Object.values(toolAcc).map((t) => ({ name: t.name, input: safeJson(t.args) })) : void 0;
|
|
72
|
+
return { content, tool_calls: tool_calls2, tokens: { input: inputTokens, output: outputTokens } };
|
|
62
73
|
}
|
|
63
74
|
const json = await res.json();
|
|
64
75
|
const msg = json.choices?.[0]?.message ?? { content: "" };
|
package/dist/agent/index.cjs
CHANGED
|
@@ -20,14 +20,123 @@ var Agent = class {
|
|
|
20
20
|
this.verifiers = config.verifiers ?? [];
|
|
21
21
|
this.max_turns = config.max_turns ?? 10;
|
|
22
22
|
}
|
|
23
|
-
/**
|
|
23
|
+
/**
|
|
24
|
+
* Render the tools as JSON schemas for the model — the agent's own tools
|
|
25
|
+
* PLUS one `delegate_to_<sub-agent>` tool per declared sub-agent, so a
|
|
26
|
+
* supervisor can actually hand work to its specialists. The Runner routes
|
|
27
|
+
* those delegate calls back into `runner.run(subAgent, task)`.
|
|
28
|
+
*/
|
|
24
29
|
toolSchemas() {
|
|
25
|
-
|
|
30
|
+
const own = this.tools.map((t) => t.schema);
|
|
31
|
+
const delegates = this.sub_agents.map((sa) => ({
|
|
32
|
+
name: subAgentToolName(sa.name),
|
|
33
|
+
description: `Delegate a self-contained subtask to the "${sa.name}" sub-agent and get back its result. ${sa.instruction.system.slice(0, 200)}`,
|
|
34
|
+
input_schema: {
|
|
35
|
+
type: "object",
|
|
36
|
+
properties: {
|
|
37
|
+
task: {
|
|
38
|
+
type: "string",
|
|
39
|
+
description: `The subtask for the "${sa.name}" sub-agent to perform, stated as a complete, standalone instruction.`
|
|
40
|
+
}
|
|
41
|
+
},
|
|
42
|
+
required: ["task"]
|
|
43
|
+
}
|
|
44
|
+
}));
|
|
45
|
+
return [...own, ...delegates];
|
|
46
|
+
}
|
|
47
|
+
/** Resolve a delegate tool-call name back to the sub-agent it targets. */
|
|
48
|
+
findSubAgent(toolName) {
|
|
49
|
+
return this.sub_agents.find((sa) => subAgentToolName(sa.name) === toolName);
|
|
26
50
|
}
|
|
27
51
|
/** True if the agent has any sub-agents (i.e., this is a supervisor). */
|
|
28
52
|
isSupervisor() {
|
|
29
53
|
return this.sub_agents.length > 0;
|
|
30
54
|
}
|
|
31
55
|
};
|
|
56
|
+
function subAgentToolName(name) {
|
|
57
|
+
return `delegate_to_${name}`.replace(/[^a-zA-Z0-9_-]/g, "_").slice(0, 64);
|
|
58
|
+
}
|
|
59
|
+
function parseMarkdownAgent(filename, content) {
|
|
60
|
+
const fmMatch = content.match(/^---\n([\s\S]*?)\n---\n?([\s\S]*)$/);
|
|
61
|
+
if (!fmMatch) return null;
|
|
62
|
+
const frontmatter = fmMatch[1];
|
|
63
|
+
const body = fmMatch[2].trim();
|
|
64
|
+
if (!body) return null;
|
|
65
|
+
const fields = {};
|
|
66
|
+
let currentKey = "";
|
|
67
|
+
for (const line of frontmatter.split("\n")) {
|
|
68
|
+
const listMatch = line.match(/^\s+-\s+(.+)$/);
|
|
69
|
+
if (listMatch && currentKey) {
|
|
70
|
+
const existing = fields[currentKey];
|
|
71
|
+
if (Array.isArray(existing)) {
|
|
72
|
+
existing.push(listMatch[1].trim());
|
|
73
|
+
} else {
|
|
74
|
+
fields[currentKey] = [listMatch[1].trim()];
|
|
75
|
+
}
|
|
76
|
+
continue;
|
|
77
|
+
}
|
|
78
|
+
const kvMatch = line.match(/^(\w[\w-]*)\s*:\s*(.*)$/);
|
|
79
|
+
if (kvMatch) {
|
|
80
|
+
const key = kvMatch[1].trim();
|
|
81
|
+
const value = kvMatch[2].trim();
|
|
82
|
+
currentKey = key;
|
|
83
|
+
fields[key] = value.replace(/^["']|["']$/g, "");
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
const name = String(fields.name || "").trim();
|
|
87
|
+
if (!name) return null;
|
|
88
|
+
const baseFilename = filename.replace(/\.md$/i, "").replace(/[^a-z0-9_-]/gi, "-").toLowerCase();
|
|
89
|
+
const id = name.toLowerCase().replace(/[^a-z0-9_-]/g, "-").replace(/-+/g, "-") || baseFilename;
|
|
90
|
+
return {
|
|
91
|
+
id,
|
|
92
|
+
name,
|
|
93
|
+
description: String(fields.description || ""),
|
|
94
|
+
model: fields.model ? String(fields.model) : void 0,
|
|
95
|
+
tools: Array.isArray(fields.tools) ? fields.tools.map(String) : void 0,
|
|
96
|
+
max_turns: fields.max_turns ? parseInt(String(fields.max_turns), 10) || void 0 : void 0,
|
|
97
|
+
system_prompt: body,
|
|
98
|
+
source: filename
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
async function loadMarkdownAgents(dir) {
|
|
102
|
+
const fs = await import('fs/promises');
|
|
103
|
+
const path = await import('path');
|
|
104
|
+
try {
|
|
105
|
+
const entries = await fs.readdir(dir);
|
|
106
|
+
const mdFiles = entries.filter((f) => f.endsWith(".md"));
|
|
107
|
+
const results = [];
|
|
108
|
+
for (const file of mdFiles) {
|
|
109
|
+
try {
|
|
110
|
+
const fullPath = path.join(dir, file);
|
|
111
|
+
const content = await fs.readFile(fullPath, "utf8");
|
|
112
|
+
const parsed = parseMarkdownAgent(file, content);
|
|
113
|
+
if (parsed) results.push(parsed);
|
|
114
|
+
} catch {
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
return results;
|
|
118
|
+
} catch {
|
|
119
|
+
return [];
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
async function loadAllMarkdownAgents(projectDir) {
|
|
123
|
+
const os = await import('os');
|
|
124
|
+
const path = await import('path');
|
|
125
|
+
const home = os.homedir();
|
|
126
|
+
const globalDir = path.join(home, ".theron", "agents");
|
|
127
|
+
const localDir = projectDir ? path.join(projectDir, ".theron", "agents") : path.join(process.cwd(), ".theron", "agents");
|
|
128
|
+
const [globalAgents, localAgents] = await Promise.all([
|
|
129
|
+
loadMarkdownAgents(globalDir),
|
|
130
|
+
loadMarkdownAgents(localDir)
|
|
131
|
+
]);
|
|
132
|
+
const byId = /* @__PURE__ */ new Map();
|
|
133
|
+
for (const a of globalAgents) byId.set(a.id, a);
|
|
134
|
+
for (const a of localAgents) byId.set(a.id, a);
|
|
135
|
+
return [...byId.values()];
|
|
136
|
+
}
|
|
32
137
|
|
|
33
138
|
exports.Agent = Agent;
|
|
139
|
+
exports.loadAllMarkdownAgents = loadAllMarkdownAgents;
|
|
140
|
+
exports.loadMarkdownAgents = loadMarkdownAgents;
|
|
141
|
+
exports.parseMarkdownAgent = parseMarkdownAgent;
|
|
142
|
+
exports.subAgentToolName = subAgentToolName;
|
package/dist/agent/index.d.cts
CHANGED
|
@@ -75,10 +75,51 @@ declare class Agent {
|
|
|
75
75
|
readonly verifiers: Verifier[];
|
|
76
76
|
readonly max_turns: number;
|
|
77
77
|
constructor(config: AgentConfig);
|
|
78
|
-
/**
|
|
78
|
+
/**
|
|
79
|
+
* Render the tools as JSON schemas for the model — the agent's own tools
|
|
80
|
+
* PLUS one `delegate_to_<sub-agent>` tool per declared sub-agent, so a
|
|
81
|
+
* supervisor can actually hand work to its specialists. The Runner routes
|
|
82
|
+
* those delegate calls back into `runner.run(subAgent, task)`.
|
|
83
|
+
*/
|
|
79
84
|
toolSchemas(): ToolSchema[];
|
|
85
|
+
/** Resolve a delegate tool-call name back to the sub-agent it targets. */
|
|
86
|
+
findSubAgent(toolName: string): Agent | undefined;
|
|
80
87
|
/** True if the agent has any sub-agents (i.e., this is a supervisor). */
|
|
81
88
|
isSupervisor(): boolean;
|
|
82
89
|
}
|
|
90
|
+
/** Canonical tool name a supervisor uses to delegate to a sub-agent. */
|
|
91
|
+
declare function subAgentToolName(name: string): string;
|
|
92
|
+
interface MarkdownAgentType {
|
|
93
|
+
/** Unique slug derived from the filename or frontmatter `name`. */
|
|
94
|
+
id: string;
|
|
95
|
+
/** Display name from frontmatter. */
|
|
96
|
+
name: string;
|
|
97
|
+
/** Human-readable description from frontmatter. */
|
|
98
|
+
description: string;
|
|
99
|
+
/** Model identifier from frontmatter (optional). */
|
|
100
|
+
model?: string;
|
|
101
|
+
/** Tool name allowlist from frontmatter (optional). */
|
|
102
|
+
tools?: string[];
|
|
103
|
+
/** Max turns from frontmatter (optional, defaults to 10). */
|
|
104
|
+
max_turns?: number;
|
|
105
|
+
/** System prompt — the Markdown body after frontmatter. */
|
|
106
|
+
system_prompt: string;
|
|
107
|
+
/** Source file path. */
|
|
108
|
+
source: string;
|
|
109
|
+
}
|
|
110
|
+
/** Parse a single Markdown agent file into a MarkdownAgentType. */
|
|
111
|
+
declare function parseMarkdownAgent(filename: string, content: string): MarkdownAgentType | null;
|
|
112
|
+
/**
|
|
113
|
+
* Load all Markdown agent types from a directory.
|
|
114
|
+
* Returns an array of parsed agent types, or an empty array if the
|
|
115
|
+
* directory doesn't exist or can't be read. Never throws.
|
|
116
|
+
*/
|
|
117
|
+
declare function loadMarkdownAgents(dir: string): Promise<MarkdownAgentType[]>;
|
|
118
|
+
/**
|
|
119
|
+
* Load Markdown agents from both global (~/.theron/agents) and project-local
|
|
120
|
+
* (.theron/agents) directories. Project-local takes precedence on id
|
|
121
|
+
* collisions. Never throws.
|
|
122
|
+
*/
|
|
123
|
+
declare function loadAllMarkdownAgents(projectDir?: string): Promise<MarkdownAgentType[]>;
|
|
83
124
|
|
|
84
|
-
export { Agent, type AgentConfig, type AgentInstruction, type AgentResult };
|
|
125
|
+
export { Agent, type AgentConfig, type AgentInstruction, type AgentResult, type MarkdownAgentType, loadAllMarkdownAgents, loadMarkdownAgents, parseMarkdownAgent, subAgentToolName };
|
package/dist/agent/index.d.ts
CHANGED
|
@@ -75,10 +75,51 @@ declare class Agent {
|
|
|
75
75
|
readonly verifiers: Verifier[];
|
|
76
76
|
readonly max_turns: number;
|
|
77
77
|
constructor(config: AgentConfig);
|
|
78
|
-
/**
|
|
78
|
+
/**
|
|
79
|
+
* Render the tools as JSON schemas for the model — the agent's own tools
|
|
80
|
+
* PLUS one `delegate_to_<sub-agent>` tool per declared sub-agent, so a
|
|
81
|
+
* supervisor can actually hand work to its specialists. The Runner routes
|
|
82
|
+
* those delegate calls back into `runner.run(subAgent, task)`.
|
|
83
|
+
*/
|
|
79
84
|
toolSchemas(): ToolSchema[];
|
|
85
|
+
/** Resolve a delegate tool-call name back to the sub-agent it targets. */
|
|
86
|
+
findSubAgent(toolName: string): Agent | undefined;
|
|
80
87
|
/** True if the agent has any sub-agents (i.e., this is a supervisor). */
|
|
81
88
|
isSupervisor(): boolean;
|
|
82
89
|
}
|
|
90
|
+
/** Canonical tool name a supervisor uses to delegate to a sub-agent. */
|
|
91
|
+
declare function subAgentToolName(name: string): string;
|
|
92
|
+
interface MarkdownAgentType {
|
|
93
|
+
/** Unique slug derived from the filename or frontmatter `name`. */
|
|
94
|
+
id: string;
|
|
95
|
+
/** Display name from frontmatter. */
|
|
96
|
+
name: string;
|
|
97
|
+
/** Human-readable description from frontmatter. */
|
|
98
|
+
description: string;
|
|
99
|
+
/** Model identifier from frontmatter (optional). */
|
|
100
|
+
model?: string;
|
|
101
|
+
/** Tool name allowlist from frontmatter (optional). */
|
|
102
|
+
tools?: string[];
|
|
103
|
+
/** Max turns from frontmatter (optional, defaults to 10). */
|
|
104
|
+
max_turns?: number;
|
|
105
|
+
/** System prompt — the Markdown body after frontmatter. */
|
|
106
|
+
system_prompt: string;
|
|
107
|
+
/** Source file path. */
|
|
108
|
+
source: string;
|
|
109
|
+
}
|
|
110
|
+
/** Parse a single Markdown agent file into a MarkdownAgentType. */
|
|
111
|
+
declare function parseMarkdownAgent(filename: string, content: string): MarkdownAgentType | null;
|
|
112
|
+
/**
|
|
113
|
+
* Load all Markdown agent types from a directory.
|
|
114
|
+
* Returns an array of parsed agent types, or an empty array if the
|
|
115
|
+
* directory doesn't exist or can't be read. Never throws.
|
|
116
|
+
*/
|
|
117
|
+
declare function loadMarkdownAgents(dir: string): Promise<MarkdownAgentType[]>;
|
|
118
|
+
/**
|
|
119
|
+
* Load Markdown agents from both global (~/.theron/agents) and project-local
|
|
120
|
+
* (.theron/agents) directories. Project-local takes precedence on id
|
|
121
|
+
* collisions. Never throws.
|
|
122
|
+
*/
|
|
123
|
+
declare function loadAllMarkdownAgents(projectDir?: string): Promise<MarkdownAgentType[]>;
|
|
83
124
|
|
|
84
|
-
export { Agent, type AgentConfig, type AgentInstruction, type AgentResult };
|
|
125
|
+
export { Agent, type AgentConfig, type AgentInstruction, type AgentResult, type MarkdownAgentType, loadAllMarkdownAgents, loadMarkdownAgents, parseMarkdownAgent, subAgentToolName };
|
package/dist/agent/index.js
CHANGED
|
@@ -18,14 +18,119 @@ var Agent = class {
|
|
|
18
18
|
this.verifiers = config.verifiers ?? [];
|
|
19
19
|
this.max_turns = config.max_turns ?? 10;
|
|
20
20
|
}
|
|
21
|
-
/**
|
|
21
|
+
/**
|
|
22
|
+
* Render the tools as JSON schemas for the model — the agent's own tools
|
|
23
|
+
* PLUS one `delegate_to_<sub-agent>` tool per declared sub-agent, so a
|
|
24
|
+
* supervisor can actually hand work to its specialists. The Runner routes
|
|
25
|
+
* those delegate calls back into `runner.run(subAgent, task)`.
|
|
26
|
+
*/
|
|
22
27
|
toolSchemas() {
|
|
23
|
-
|
|
28
|
+
const own = this.tools.map((t) => t.schema);
|
|
29
|
+
const delegates = this.sub_agents.map((sa) => ({
|
|
30
|
+
name: subAgentToolName(sa.name),
|
|
31
|
+
description: `Delegate a self-contained subtask to the "${sa.name}" sub-agent and get back its result. ${sa.instruction.system.slice(0, 200)}`,
|
|
32
|
+
input_schema: {
|
|
33
|
+
type: "object",
|
|
34
|
+
properties: {
|
|
35
|
+
task: {
|
|
36
|
+
type: "string",
|
|
37
|
+
description: `The subtask for the "${sa.name}" sub-agent to perform, stated as a complete, standalone instruction.`
|
|
38
|
+
}
|
|
39
|
+
},
|
|
40
|
+
required: ["task"]
|
|
41
|
+
}
|
|
42
|
+
}));
|
|
43
|
+
return [...own, ...delegates];
|
|
44
|
+
}
|
|
45
|
+
/** Resolve a delegate tool-call name back to the sub-agent it targets. */
|
|
46
|
+
findSubAgent(toolName) {
|
|
47
|
+
return this.sub_agents.find((sa) => subAgentToolName(sa.name) === toolName);
|
|
24
48
|
}
|
|
25
49
|
/** True if the agent has any sub-agents (i.e., this is a supervisor). */
|
|
26
50
|
isSupervisor() {
|
|
27
51
|
return this.sub_agents.length > 0;
|
|
28
52
|
}
|
|
29
53
|
};
|
|
54
|
+
function subAgentToolName(name) {
|
|
55
|
+
return `delegate_to_${name}`.replace(/[^a-zA-Z0-9_-]/g, "_").slice(0, 64);
|
|
56
|
+
}
|
|
57
|
+
function parseMarkdownAgent(filename, content) {
|
|
58
|
+
const fmMatch = content.match(/^---\n([\s\S]*?)\n---\n?([\s\S]*)$/);
|
|
59
|
+
if (!fmMatch) return null;
|
|
60
|
+
const frontmatter = fmMatch[1];
|
|
61
|
+
const body = fmMatch[2].trim();
|
|
62
|
+
if (!body) return null;
|
|
63
|
+
const fields = {};
|
|
64
|
+
let currentKey = "";
|
|
65
|
+
for (const line of frontmatter.split("\n")) {
|
|
66
|
+
const listMatch = line.match(/^\s+-\s+(.+)$/);
|
|
67
|
+
if (listMatch && currentKey) {
|
|
68
|
+
const existing = fields[currentKey];
|
|
69
|
+
if (Array.isArray(existing)) {
|
|
70
|
+
existing.push(listMatch[1].trim());
|
|
71
|
+
} else {
|
|
72
|
+
fields[currentKey] = [listMatch[1].trim()];
|
|
73
|
+
}
|
|
74
|
+
continue;
|
|
75
|
+
}
|
|
76
|
+
const kvMatch = line.match(/^(\w[\w-]*)\s*:\s*(.*)$/);
|
|
77
|
+
if (kvMatch) {
|
|
78
|
+
const key = kvMatch[1].trim();
|
|
79
|
+
const value = kvMatch[2].trim();
|
|
80
|
+
currentKey = key;
|
|
81
|
+
fields[key] = value.replace(/^["']|["']$/g, "");
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
const name = String(fields.name || "").trim();
|
|
85
|
+
if (!name) return null;
|
|
86
|
+
const baseFilename = filename.replace(/\.md$/i, "").replace(/[^a-z0-9_-]/gi, "-").toLowerCase();
|
|
87
|
+
const id = name.toLowerCase().replace(/[^a-z0-9_-]/g, "-").replace(/-+/g, "-") || baseFilename;
|
|
88
|
+
return {
|
|
89
|
+
id,
|
|
90
|
+
name,
|
|
91
|
+
description: String(fields.description || ""),
|
|
92
|
+
model: fields.model ? String(fields.model) : void 0,
|
|
93
|
+
tools: Array.isArray(fields.tools) ? fields.tools.map(String) : void 0,
|
|
94
|
+
max_turns: fields.max_turns ? parseInt(String(fields.max_turns), 10) || void 0 : void 0,
|
|
95
|
+
system_prompt: body,
|
|
96
|
+
source: filename
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
async function loadMarkdownAgents(dir) {
|
|
100
|
+
const fs = await import('fs/promises');
|
|
101
|
+
const path = await import('path');
|
|
102
|
+
try {
|
|
103
|
+
const entries = await fs.readdir(dir);
|
|
104
|
+
const mdFiles = entries.filter((f) => f.endsWith(".md"));
|
|
105
|
+
const results = [];
|
|
106
|
+
for (const file of mdFiles) {
|
|
107
|
+
try {
|
|
108
|
+
const fullPath = path.join(dir, file);
|
|
109
|
+
const content = await fs.readFile(fullPath, "utf8");
|
|
110
|
+
const parsed = parseMarkdownAgent(file, content);
|
|
111
|
+
if (parsed) results.push(parsed);
|
|
112
|
+
} catch {
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
return results;
|
|
116
|
+
} catch {
|
|
117
|
+
return [];
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
async function loadAllMarkdownAgents(projectDir) {
|
|
121
|
+
const os = await import('os');
|
|
122
|
+
const path = await import('path');
|
|
123
|
+
const home = os.homedir();
|
|
124
|
+
const globalDir = path.join(home, ".theron", "agents");
|
|
125
|
+
const localDir = projectDir ? path.join(projectDir, ".theron", "agents") : path.join(process.cwd(), ".theron", "agents");
|
|
126
|
+
const [globalAgents, localAgents] = await Promise.all([
|
|
127
|
+
loadMarkdownAgents(globalDir),
|
|
128
|
+
loadMarkdownAgents(localDir)
|
|
129
|
+
]);
|
|
130
|
+
const byId = /* @__PURE__ */ new Map();
|
|
131
|
+
for (const a of globalAgents) byId.set(a.id, a);
|
|
132
|
+
for (const a of localAgents) byId.set(a.id, a);
|
|
133
|
+
return [...byId.values()];
|
|
134
|
+
}
|
|
30
135
|
|
|
31
|
-
export { Agent };
|
|
136
|
+
export { Agent, loadAllMarkdownAgents, loadMarkdownAgents, parseMarkdownAgent, subAgentToolName };
|
package/dist/council/index.cjs
CHANGED
|
@@ -1,12 +1,14 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
3
|
// src/council/index.ts
|
|
4
|
+
var sentenceClaimExtractor = (output) => output.split(/(?<=[.!?])\s+/).map((s) => s.trim()).filter((s) => s.length > 0).map((text) => ({ text, confidence: 1, type: "assertion" }));
|
|
4
5
|
var Council = class {
|
|
5
6
|
name;
|
|
6
7
|
specialists;
|
|
7
8
|
verifiers;
|
|
8
9
|
reconciler;
|
|
9
10
|
specialist_timeout_ms;
|
|
11
|
+
claimExtractor;
|
|
10
12
|
constructor(config) {
|
|
11
13
|
if (!config.name) throw new Error("Council requires a `name`.");
|
|
12
14
|
if (!config.specialists || config.specialists.length === 0) {
|
|
@@ -17,6 +19,7 @@ var Council = class {
|
|
|
17
19
|
this.verifiers = config.verifiers ?? [];
|
|
18
20
|
this.reconciler = config.reconciler ?? deterministicClaimMerge;
|
|
19
21
|
this.specialist_timeout_ms = config.specialist_timeout_ms ?? 3e4;
|
|
22
|
+
this.claimExtractor = config.claimExtractor;
|
|
20
23
|
}
|
|
21
24
|
/**
|
|
22
25
|
* Convenience entry point — pointed at the Runner you've already constructed.
|
|
@@ -66,3 +69,4 @@ var deterministicClaimMerge = async (specialists) => {
|
|
|
66
69
|
};
|
|
67
70
|
|
|
68
71
|
exports.Council = Council;
|
|
72
|
+
exports.sentenceClaimExtractor = sentenceClaimExtractor;
|
package/dist/council/index.d.cts
CHANGED
|
@@ -60,7 +60,30 @@ interface CouncilConfig {
|
|
|
60
60
|
reconciler?: Reconciler;
|
|
61
61
|
/** Optional timeout per specialist (ms). Slow specialists are dropped. */
|
|
62
62
|
specialist_timeout_ms?: number;
|
|
63
|
+
/**
|
|
64
|
+
* Optional claim extractor. The default deterministic reconciler votes over
|
|
65
|
+
* the per-specialist `claims` arrays — with no extractor those arrays are
|
|
66
|
+
* empty and the reconciler can only fall back to the first specialist's
|
|
67
|
+
* output ("refuted"). Supply an extractor (e.g. a sentence splitter, or a
|
|
68
|
+
* structured parser keyed on your specialists' output format) to enable
|
|
69
|
+
* real cross-specialist ratification. See `sentenceClaimExtractor`.
|
|
70
|
+
*/
|
|
71
|
+
claimExtractor?: ClaimExtractor;
|
|
63
72
|
}
|
|
73
|
+
/** Turn a specialist's raw output into a list of claims for voting. */
|
|
74
|
+
type ClaimExtractor = (output: string) => Array<{
|
|
75
|
+
text: string;
|
|
76
|
+
confidence: number;
|
|
77
|
+
type: string;
|
|
78
|
+
}>;
|
|
79
|
+
/**
|
|
80
|
+
* A simple, dependency-free claim extractor: split the output into sentences
|
|
81
|
+
* and treat each as an assertion at full confidence. Paraphrase-insensitive
|
|
82
|
+
* (the default reconciler matches claim text exactly, lowercased), so it
|
|
83
|
+
* ratifies only verbatim-agreeing claims — good enough for short, factual
|
|
84
|
+
* specialist answers; swap in a semantic extractor for prose.
|
|
85
|
+
*/
|
|
86
|
+
declare const sentenceClaimExtractor: ClaimExtractor;
|
|
64
87
|
/**
|
|
65
88
|
* The Council primitive.
|
|
66
89
|
*
|
|
@@ -84,6 +107,7 @@ declare class Council {
|
|
|
84
107
|
readonly verifiers: Verifier[];
|
|
85
108
|
readonly reconciler: Reconciler;
|
|
86
109
|
readonly specialist_timeout_ms: number;
|
|
110
|
+
readonly claimExtractor: ClaimExtractor | undefined;
|
|
87
111
|
constructor(config: CouncilConfig);
|
|
88
112
|
/**
|
|
89
113
|
* Convenience entry point — pointed at the Runner you've already constructed.
|
|
@@ -93,4 +117,4 @@ declare class Council {
|
|
|
93
117
|
deliberate(_query: string): Promise<CouncilOutput>;
|
|
94
118
|
}
|
|
95
119
|
|
|
96
|
-
export { Council, type CouncilConfig, type CouncilOutput, type CouncilSpecialistOutput, type Reconciler };
|
|
120
|
+
export { type ClaimExtractor, Council, type CouncilConfig, type CouncilOutput, type CouncilSpecialistOutput, type Reconciler, sentenceClaimExtractor };
|
package/dist/council/index.d.ts
CHANGED
|
@@ -60,7 +60,30 @@ interface CouncilConfig {
|
|
|
60
60
|
reconciler?: Reconciler;
|
|
61
61
|
/** Optional timeout per specialist (ms). Slow specialists are dropped. */
|
|
62
62
|
specialist_timeout_ms?: number;
|
|
63
|
+
/**
|
|
64
|
+
* Optional claim extractor. The default deterministic reconciler votes over
|
|
65
|
+
* the per-specialist `claims` arrays — with no extractor those arrays are
|
|
66
|
+
* empty and the reconciler can only fall back to the first specialist's
|
|
67
|
+
* output ("refuted"). Supply an extractor (e.g. a sentence splitter, or a
|
|
68
|
+
* structured parser keyed on your specialists' output format) to enable
|
|
69
|
+
* real cross-specialist ratification. See `sentenceClaimExtractor`.
|
|
70
|
+
*/
|
|
71
|
+
claimExtractor?: ClaimExtractor;
|
|
63
72
|
}
|
|
73
|
+
/** Turn a specialist's raw output into a list of claims for voting. */
|
|
74
|
+
type ClaimExtractor = (output: string) => Array<{
|
|
75
|
+
text: string;
|
|
76
|
+
confidence: number;
|
|
77
|
+
type: string;
|
|
78
|
+
}>;
|
|
79
|
+
/**
|
|
80
|
+
* A simple, dependency-free claim extractor: split the output into sentences
|
|
81
|
+
* and treat each as an assertion at full confidence. Paraphrase-insensitive
|
|
82
|
+
* (the default reconciler matches claim text exactly, lowercased), so it
|
|
83
|
+
* ratifies only verbatim-agreeing claims — good enough for short, factual
|
|
84
|
+
* specialist answers; swap in a semantic extractor for prose.
|
|
85
|
+
*/
|
|
86
|
+
declare const sentenceClaimExtractor: ClaimExtractor;
|
|
64
87
|
/**
|
|
65
88
|
* The Council primitive.
|
|
66
89
|
*
|
|
@@ -84,6 +107,7 @@ declare class Council {
|
|
|
84
107
|
readonly verifiers: Verifier[];
|
|
85
108
|
readonly reconciler: Reconciler;
|
|
86
109
|
readonly specialist_timeout_ms: number;
|
|
110
|
+
readonly claimExtractor: ClaimExtractor | undefined;
|
|
87
111
|
constructor(config: CouncilConfig);
|
|
88
112
|
/**
|
|
89
113
|
* Convenience entry point — pointed at the Runner you've already constructed.
|
|
@@ -93,4 +117,4 @@ declare class Council {
|
|
|
93
117
|
deliberate(_query: string): Promise<CouncilOutput>;
|
|
94
118
|
}
|
|
95
119
|
|
|
96
|
-
export { Council, type CouncilConfig, type CouncilOutput, type CouncilSpecialistOutput, type Reconciler };
|
|
120
|
+
export { type ClaimExtractor, Council, type CouncilConfig, type CouncilOutput, type CouncilSpecialistOutput, type Reconciler, sentenceClaimExtractor };
|
package/dist/council/index.js
CHANGED
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
// src/council/index.ts
|
|
2
|
+
var sentenceClaimExtractor = (output) => output.split(/(?<=[.!?])\s+/).map((s) => s.trim()).filter((s) => s.length > 0).map((text) => ({ text, confidence: 1, type: "assertion" }));
|
|
2
3
|
var Council = class {
|
|
3
4
|
name;
|
|
4
5
|
specialists;
|
|
5
6
|
verifiers;
|
|
6
7
|
reconciler;
|
|
7
8
|
specialist_timeout_ms;
|
|
9
|
+
claimExtractor;
|
|
8
10
|
constructor(config) {
|
|
9
11
|
if (!config.name) throw new Error("Council requires a `name`.");
|
|
10
12
|
if (!config.specialists || config.specialists.length === 0) {
|
|
@@ -15,6 +17,7 @@ var Council = class {
|
|
|
15
17
|
this.verifiers = config.verifiers ?? [];
|
|
16
18
|
this.reconciler = config.reconciler ?? deterministicClaimMerge;
|
|
17
19
|
this.specialist_timeout_ms = config.specialist_timeout_ms ?? 3e4;
|
|
20
|
+
this.claimExtractor = config.claimExtractor;
|
|
18
21
|
}
|
|
19
22
|
/**
|
|
20
23
|
* Convenience entry point — pointed at the Runner you've already constructed.
|
|
@@ -63,4 +66,4 @@ var deterministicClaimMerge = async (specialists) => {
|
|
|
63
66
|
return { answer, consensus, disagreements: disagreements.length > 0 ? disagreements : void 0 };
|
|
64
67
|
};
|
|
65
68
|
|
|
66
|
-
export { Council };
|
|
69
|
+
export { Council, sentenceClaimExtractor };
|