@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/dist/index.js
CHANGED
|
@@ -1,5 +1,11 @@
|
|
|
1
1
|
import { z } from 'zod';
|
|
2
2
|
export { z as zod } from 'zod';
|
|
3
|
+
import { readFile, mkdir, writeFile, rm, mkdtemp } from 'fs/promises';
|
|
4
|
+
import { tmpdir } from 'os';
|
|
5
|
+
import { dirname, join, isAbsolute, resolve, relative, sep } from 'path';
|
|
6
|
+
import { execFile } from 'child_process';
|
|
7
|
+
import { promisify } from 'util';
|
|
8
|
+
import { randomUUID } from 'crypto';
|
|
3
9
|
|
|
4
10
|
// src/agent/index.ts
|
|
5
11
|
var Agent = class {
|
|
@@ -21,23 +27,213 @@ var Agent = class {
|
|
|
21
27
|
this.verifiers = config.verifiers ?? [];
|
|
22
28
|
this.max_turns = config.max_turns ?? 10;
|
|
23
29
|
}
|
|
24
|
-
/**
|
|
30
|
+
/**
|
|
31
|
+
* Render the tools as JSON schemas for the model — the agent's own tools
|
|
32
|
+
* PLUS one `delegate_to_<sub-agent>` tool per declared sub-agent, so a
|
|
33
|
+
* supervisor can actually hand work to its specialists. The Runner routes
|
|
34
|
+
* those delegate calls back into `runner.run(subAgent, task)`.
|
|
35
|
+
*/
|
|
25
36
|
toolSchemas() {
|
|
26
|
-
|
|
37
|
+
const own = this.tools.map((t) => t.schema);
|
|
38
|
+
const delegates = this.sub_agents.map((sa) => ({
|
|
39
|
+
name: subAgentToolName(sa.name),
|
|
40
|
+
description: `Delegate a self-contained subtask to the "${sa.name}" sub-agent and get back its result. ${sa.instruction.system.slice(0, 200)}`,
|
|
41
|
+
input_schema: {
|
|
42
|
+
type: "object",
|
|
43
|
+
properties: {
|
|
44
|
+
task: {
|
|
45
|
+
type: "string",
|
|
46
|
+
description: `The subtask for the "${sa.name}" sub-agent to perform, stated as a complete, standalone instruction.`
|
|
47
|
+
}
|
|
48
|
+
},
|
|
49
|
+
required: ["task"]
|
|
50
|
+
}
|
|
51
|
+
}));
|
|
52
|
+
return [...own, ...delegates];
|
|
53
|
+
}
|
|
54
|
+
/** Resolve a delegate tool-call name back to the sub-agent it targets. */
|
|
55
|
+
findSubAgent(toolName) {
|
|
56
|
+
return this.sub_agents.find((sa) => subAgentToolName(sa.name) === toolName);
|
|
27
57
|
}
|
|
28
58
|
/** True if the agent has any sub-agents (i.e., this is a supervisor). */
|
|
29
59
|
isSupervisor() {
|
|
30
60
|
return this.sub_agents.length > 0;
|
|
31
61
|
}
|
|
32
62
|
};
|
|
63
|
+
function subAgentToolName(name) {
|
|
64
|
+
return `delegate_to_${name}`.replace(/[^a-zA-Z0-9_-]/g, "_").slice(0, 64);
|
|
65
|
+
}
|
|
66
|
+
function parseMarkdownAgent(filename, content) {
|
|
67
|
+
const fmMatch = content.match(/^---\n([\s\S]*?)\n---\n?([\s\S]*)$/);
|
|
68
|
+
if (!fmMatch) return null;
|
|
69
|
+
const frontmatter = fmMatch[1];
|
|
70
|
+
const body = fmMatch[2].trim();
|
|
71
|
+
if (!body) return null;
|
|
72
|
+
const fields = {};
|
|
73
|
+
let currentKey = "";
|
|
74
|
+
for (const line of frontmatter.split("\n")) {
|
|
75
|
+
const listMatch = line.match(/^\s+-\s+(.+)$/);
|
|
76
|
+
if (listMatch && currentKey) {
|
|
77
|
+
const existing = fields[currentKey];
|
|
78
|
+
if (Array.isArray(existing)) {
|
|
79
|
+
existing.push(listMatch[1].trim());
|
|
80
|
+
} else {
|
|
81
|
+
fields[currentKey] = [listMatch[1].trim()];
|
|
82
|
+
}
|
|
83
|
+
continue;
|
|
84
|
+
}
|
|
85
|
+
const kvMatch = line.match(/^(\w[\w-]*)\s*:\s*(.*)$/);
|
|
86
|
+
if (kvMatch) {
|
|
87
|
+
const key = kvMatch[1].trim();
|
|
88
|
+
const value = kvMatch[2].trim();
|
|
89
|
+
currentKey = key;
|
|
90
|
+
fields[key] = value.replace(/^["']|["']$/g, "");
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
const name = String(fields.name || "").trim();
|
|
94
|
+
if (!name) return null;
|
|
95
|
+
const baseFilename = filename.replace(/\.md$/i, "").replace(/[^a-z0-9_-]/gi, "-").toLowerCase();
|
|
96
|
+
const id = name.toLowerCase().replace(/[^a-z0-9_-]/g, "-").replace(/-+/g, "-") || baseFilename;
|
|
97
|
+
return {
|
|
98
|
+
id,
|
|
99
|
+
name,
|
|
100
|
+
description: String(fields.description || ""),
|
|
101
|
+
model: fields.model ? String(fields.model) : void 0,
|
|
102
|
+
tools: Array.isArray(fields.tools) ? fields.tools.map(String) : void 0,
|
|
103
|
+
max_turns: fields.max_turns ? parseInt(String(fields.max_turns), 10) || void 0 : void 0,
|
|
104
|
+
system_prompt: body,
|
|
105
|
+
source: filename
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
async function loadMarkdownAgents(dir) {
|
|
109
|
+
const fs = await import('fs/promises');
|
|
110
|
+
const path = await import('path');
|
|
111
|
+
try {
|
|
112
|
+
const entries = await fs.readdir(dir);
|
|
113
|
+
const mdFiles = entries.filter((f) => f.endsWith(".md"));
|
|
114
|
+
const results = [];
|
|
115
|
+
for (const file of mdFiles) {
|
|
116
|
+
try {
|
|
117
|
+
const fullPath = path.join(dir, file);
|
|
118
|
+
const content = await fs.readFile(fullPath, "utf8");
|
|
119
|
+
const parsed = parseMarkdownAgent(file, content);
|
|
120
|
+
if (parsed) results.push(parsed);
|
|
121
|
+
} catch {
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
return results;
|
|
125
|
+
} catch {
|
|
126
|
+
return [];
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
async function loadAllMarkdownAgents(projectDir) {
|
|
130
|
+
const os = await import('os');
|
|
131
|
+
const path = await import('path');
|
|
132
|
+
const home = os.homedir();
|
|
133
|
+
const globalDir = path.join(home, ".theron", "agents");
|
|
134
|
+
const localDir = projectDir ? path.join(projectDir, ".theron", "agents") : path.join(process.cwd(), ".theron", "agents");
|
|
135
|
+
const [globalAgents, localAgents] = await Promise.all([
|
|
136
|
+
loadMarkdownAgents(globalDir),
|
|
137
|
+
loadMarkdownAgents(localDir)
|
|
138
|
+
]);
|
|
139
|
+
const byId = /* @__PURE__ */ new Map();
|
|
140
|
+
for (const a of globalAgents) byId.set(a.id, a);
|
|
141
|
+
for (const a of localAgents) byId.set(a.id, a);
|
|
142
|
+
return [...byId.values()];
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
// src/skills/index.ts
|
|
146
|
+
function parseMarkdownSkill(filename, content) {
|
|
147
|
+
const fmMatch = content.match(/^---\n([\s\S]*?)\n---\n?([\s\S]*)$/);
|
|
148
|
+
if (!fmMatch) return null;
|
|
149
|
+
const frontmatter = fmMatch[1];
|
|
150
|
+
const body = fmMatch[2].trim();
|
|
151
|
+
if (!body) return null;
|
|
152
|
+
const fields = {};
|
|
153
|
+
let currentKey = "";
|
|
154
|
+
for (const line of frontmatter.split("\n")) {
|
|
155
|
+
const listMatch = line.match(/^\s+-\s+(.+)$/);
|
|
156
|
+
if (listMatch && currentKey) {
|
|
157
|
+
const existing = fields[currentKey];
|
|
158
|
+
if (Array.isArray(existing)) existing.push(listMatch[1].trim());
|
|
159
|
+
else fields[currentKey] = [listMatch[1].trim()];
|
|
160
|
+
continue;
|
|
161
|
+
}
|
|
162
|
+
const kvMatch = line.match(/^([\w-]+)\s*:\s*(.*)$/);
|
|
163
|
+
if (kvMatch) {
|
|
164
|
+
currentKey = kvMatch[1].trim();
|
|
165
|
+
fields[currentKey] = kvMatch[2].trim().replace(/^["']|["']$/g, "");
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
const rawName = String(fields.name || filename.replace(/\.md$/i, "")).trim();
|
|
169
|
+
const name = rawName.toLowerCase().replace(/[^a-z0-9_-]/g, "-").replace(/-+/g, "-");
|
|
170
|
+
if (!name) return null;
|
|
171
|
+
let allowedTools;
|
|
172
|
+
const at = fields["allowed-tools"] ?? fields.allowedTools ?? fields.tools;
|
|
173
|
+
if (Array.isArray(at)) allowedTools = at.map(String);
|
|
174
|
+
else if (typeof at === "string" && at.trim()) {
|
|
175
|
+
allowedTools = at.split(",").map((s) => s.trim()).filter(Boolean);
|
|
176
|
+
}
|
|
177
|
+
return {
|
|
178
|
+
name,
|
|
179
|
+
description: String(fields.description || ""),
|
|
180
|
+
body,
|
|
181
|
+
allowedTools,
|
|
182
|
+
model: fields.model ? String(fields.model) : void 0,
|
|
183
|
+
source: filename
|
|
184
|
+
};
|
|
185
|
+
}
|
|
186
|
+
async function loadMarkdownSkills(dir) {
|
|
187
|
+
const fs = await import('fs/promises');
|
|
188
|
+
const path = await import('path');
|
|
189
|
+
const out = [];
|
|
190
|
+
try {
|
|
191
|
+
const entries = await fs.readdir(dir, { withFileTypes: true });
|
|
192
|
+
for (const ent of entries) {
|
|
193
|
+
try {
|
|
194
|
+
if (ent.isFile() && ent.name.endsWith(".md")) {
|
|
195
|
+
const full = path.join(dir, ent.name);
|
|
196
|
+
const parsed = parseMarkdownSkill(ent.name, await fs.readFile(full, "utf8"));
|
|
197
|
+
if (parsed) out.push(parsed);
|
|
198
|
+
} else if (ent.isDirectory()) {
|
|
199
|
+
const full = path.join(dir, ent.name, "SKILL.md");
|
|
200
|
+
const buf = await fs.readFile(full, "utf8").catch(() => null);
|
|
201
|
+
if (buf) {
|
|
202
|
+
const parsed = parseMarkdownSkill(`${ent.name}/SKILL.md`, buf);
|
|
203
|
+
if (parsed) out.push({ ...parsed, name: parsed.name || ent.name.toLowerCase() });
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
} catch {
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
} catch {
|
|
210
|
+
}
|
|
211
|
+
return out;
|
|
212
|
+
}
|
|
213
|
+
async function loadAllMarkdownSkills(projectDir) {
|
|
214
|
+
const os = await import('os');
|
|
215
|
+
const path = await import('path');
|
|
216
|
+
const globalDir = path.join(os.homedir(), ".theron", "skills");
|
|
217
|
+
const localDir = path.join(projectDir ?? process.cwd(), ".theron", "skills");
|
|
218
|
+
const [globalSkills, localSkills] = await Promise.all([
|
|
219
|
+
loadMarkdownSkills(globalDir),
|
|
220
|
+
loadMarkdownSkills(localDir)
|
|
221
|
+
]);
|
|
222
|
+
const byName = /* @__PURE__ */ new Map();
|
|
223
|
+
for (const s of globalSkills) byName.set(s.name, s);
|
|
224
|
+
for (const s of localSkills) byName.set(s.name, s);
|
|
225
|
+
return [...byName.values()];
|
|
226
|
+
}
|
|
33
227
|
|
|
34
228
|
// src/council/index.ts
|
|
229
|
+
var sentenceClaimExtractor = (output) => output.split(/(?<=[.!?])\s+/).map((s) => s.trim()).filter((s) => s.length > 0).map((text) => ({ text, confidence: 1, type: "assertion" }));
|
|
35
230
|
var Council = class {
|
|
36
231
|
name;
|
|
37
232
|
specialists;
|
|
38
233
|
verifiers;
|
|
39
234
|
reconciler;
|
|
40
235
|
specialist_timeout_ms;
|
|
236
|
+
claimExtractor;
|
|
41
237
|
constructor(config) {
|
|
42
238
|
if (!config.name) throw new Error("Council requires a `name`.");
|
|
43
239
|
if (!config.specialists || config.specialists.length === 0) {
|
|
@@ -48,6 +244,7 @@ var Council = class {
|
|
|
48
244
|
this.verifiers = config.verifiers ?? [];
|
|
49
245
|
this.reconciler = config.reconciler ?? deterministicClaimMerge;
|
|
50
246
|
this.specialist_timeout_ms = config.specialist_timeout_ms ?? 3e4;
|
|
247
|
+
this.claimExtractor = config.claimExtractor;
|
|
51
248
|
}
|
|
52
249
|
/**
|
|
53
250
|
* Convenience entry point — pointed at the Runner you've already constructed.
|
|
@@ -213,22 +410,57 @@ function defineTool(opts) {
|
|
|
213
410
|
};
|
|
214
411
|
}
|
|
215
412
|
function zodToJsonSchema(schema) {
|
|
413
|
+
const description = schema._def?.description;
|
|
414
|
+
const withDesc = (s) => description ? { ...s, description } : s;
|
|
415
|
+
if (schema instanceof z.ZodOptional) return zodToJsonSchema(schema.unwrap());
|
|
416
|
+
if (schema instanceof z.ZodDefault) {
|
|
417
|
+
const innerType = schema._def.innerType;
|
|
418
|
+
const inner = zodToJsonSchema(innerType);
|
|
419
|
+
let def;
|
|
420
|
+
try {
|
|
421
|
+
def = schema._def.defaultValue?.();
|
|
422
|
+
} catch {
|
|
423
|
+
def = void 0;
|
|
424
|
+
}
|
|
425
|
+
return withDesc(def === void 0 ? inner : { ...inner, default: def });
|
|
426
|
+
}
|
|
427
|
+
if (schema instanceof z.ZodNullable) {
|
|
428
|
+
return withDesc({ ...zodToJsonSchema(schema.unwrap()), nullable: true });
|
|
429
|
+
}
|
|
216
430
|
if (schema instanceof z.ZodObject) {
|
|
217
431
|
const properties = {};
|
|
218
432
|
const required = [];
|
|
219
433
|
for (const [key, value] of Object.entries(schema.shape)) {
|
|
220
|
-
|
|
221
|
-
|
|
434
|
+
const field = value;
|
|
435
|
+
properties[key] = zodToJsonSchema(field);
|
|
436
|
+
if (!(field instanceof z.ZodOptional) && !(field instanceof z.ZodDefault)) {
|
|
437
|
+
required.push(key);
|
|
438
|
+
}
|
|
222
439
|
}
|
|
223
|
-
return { type: "object", properties, ...required.length > 0 ? { required } : {} };
|
|
440
|
+
return withDesc({ type: "object", properties, ...required.length > 0 ? { required } : {} });
|
|
224
441
|
}
|
|
225
|
-
if (schema instanceof z.ZodString) return { type: "string" };
|
|
226
|
-
if (schema instanceof z.ZodNumber) return { type: "number" };
|
|
227
|
-
if (schema instanceof z.ZodBoolean) return { type: "boolean" };
|
|
228
|
-
if (schema instanceof z.ZodArray) return { type: "array", items: zodToJsonSchema(schema.element) };
|
|
229
|
-
if (schema instanceof z.
|
|
230
|
-
if (schema instanceof z.
|
|
231
|
-
|
|
442
|
+
if (schema instanceof z.ZodString) return withDesc({ type: "string" });
|
|
443
|
+
if (schema instanceof z.ZodNumber) return withDesc({ type: "number" });
|
|
444
|
+
if (schema instanceof z.ZodBoolean) return withDesc({ type: "boolean" });
|
|
445
|
+
if (schema instanceof z.ZodArray) return withDesc({ type: "array", items: zodToJsonSchema(schema.element) });
|
|
446
|
+
if (schema instanceof z.ZodEnum) return withDesc({ type: "string", enum: schema.options });
|
|
447
|
+
if (schema instanceof z.ZodLiteral) {
|
|
448
|
+
const val = schema.value;
|
|
449
|
+
const t = typeof val === "number" ? "number" : typeof val === "boolean" ? "boolean" : "string";
|
|
450
|
+
return withDesc({ type: t, enum: [val] });
|
|
451
|
+
}
|
|
452
|
+
if (schema instanceof z.ZodUnion) {
|
|
453
|
+
const options = schema._def.options;
|
|
454
|
+
return withDesc({ anyOf: options.map((o) => zodToJsonSchema(o)) });
|
|
455
|
+
}
|
|
456
|
+
if (schema instanceof z.ZodRecord) {
|
|
457
|
+
const valueType = schema._def.valueType;
|
|
458
|
+
return withDesc({
|
|
459
|
+
type: "object",
|
|
460
|
+
additionalProperties: valueType ? zodToJsonSchema(valueType) : true
|
|
461
|
+
});
|
|
462
|
+
}
|
|
463
|
+
return withDesc({ type: "string" });
|
|
232
464
|
}
|
|
233
465
|
|
|
234
466
|
// src/tools/local-contract.ts
|
|
@@ -280,17 +512,30 @@ var LOCAL_TOOL_PARAMETERS = {
|
|
|
280
512
|
Grep: {
|
|
281
513
|
type: "object",
|
|
282
514
|
properties: {
|
|
283
|
-
pattern: { type: "string", description: "Regex pattern." },
|
|
515
|
+
pattern: { type: "string", description: "Regex pattern (ripgrep syntax). Use fixed_strings for a literal search." },
|
|
284
516
|
path: { type: "string", description: "Optional file or directory to limit the search." },
|
|
285
|
-
glob: { type: "string", description: "Optional glob filter, e.g. '*.ts'." },
|
|
286
|
-
|
|
517
|
+
glob: { type: "string", description: "Optional glob filter, e.g. '*.ts' or 'src/**/*.tsx'." },
|
|
518
|
+
type: { type: "string", description: "Optional file-type filter (ripgrep --type), e.g. 'ts', 'py', 'rust'. More efficient than glob for language filters." },
|
|
519
|
+
case_insensitive: { type: "boolean", default: false, description: "Case-insensitive match." },
|
|
520
|
+
output_mode: {
|
|
521
|
+
type: "string",
|
|
522
|
+
enum: ["content", "files_with_matches", "count"],
|
|
523
|
+
description: "What to return: 'content' = matching lines with file:line (default), 'files_with_matches' = just the file paths, 'count' = per-file match counts.",
|
|
524
|
+
default: "content"
|
|
525
|
+
},
|
|
526
|
+
context_lines: { type: "number", description: "Lines of context to show before AND after each match (ripgrep -C). Only applies to output_mode 'content'." },
|
|
527
|
+
multiline: { type: "boolean", default: false, description: "Allow the pattern to span line boundaries (ripgrep --multiline; '.' matches newlines)." },
|
|
528
|
+
fixed_strings: { type: "boolean", default: false, description: "Treat the pattern as a literal string, not a regex (ripgrep -F)." }
|
|
287
529
|
},
|
|
288
530
|
required: ["pattern"]
|
|
289
531
|
},
|
|
290
532
|
LS: {
|
|
291
533
|
type: "object",
|
|
292
534
|
properties: {
|
|
293
|
-
path: { type: "string", description: "Path to list. Defaults to the working directory." }
|
|
535
|
+
path: { type: "string", description: "Path to list. Defaults to the working directory." },
|
|
536
|
+
show_hidden: { type: "boolean", default: false, description: "Include dotfiles (.env, .gitignore, .github, etc.) in the listing." },
|
|
537
|
+
recursive: { type: "boolean", default: false, description: "List subdirectories recursively (up to `depth` levels)." },
|
|
538
|
+
depth: { type: "number", description: "Max recursion depth when recursive=true (default 3)." }
|
|
294
539
|
}
|
|
295
540
|
}
|
|
296
541
|
};
|
|
@@ -411,6 +656,105 @@ var VerifierKernels = {
|
|
|
411
656
|
})
|
|
412
657
|
};
|
|
413
658
|
|
|
659
|
+
// src/reasoning-cert/index.ts
|
|
660
|
+
var ARITH = /(-?\d+(?:\.\d+)?)\s*([+\-*/])\s*(-?\d+(?:\.\d+)?)\s*=\s*(-?\d+(?:\.\d+)?)/g;
|
|
661
|
+
async function sha256Hex(s) {
|
|
662
|
+
const buf = await globalThis.crypto.subtle.digest("SHA-256", new TextEncoder().encode(s));
|
|
663
|
+
return "sha256:" + [...new Uint8Array(buf)].map((b) => b.toString(16).padStart(2, "0")).join("");
|
|
664
|
+
}
|
|
665
|
+
async function certifyArithmetic(text) {
|
|
666
|
+
const claim = String(text ?? "");
|
|
667
|
+
const matched = [...claim.matchAll(ARITH)].length;
|
|
668
|
+
const res = await VerifierKernels.arithmetic.check(claim);
|
|
669
|
+
const verdict = matched === 0 ? "ABSTAIN" : res.pass ? "PASS" : "FAIL";
|
|
670
|
+
return {
|
|
671
|
+
tier: "arithmetic",
|
|
672
|
+
oracle_id: "js_runtime",
|
|
673
|
+
oracle_version: typeof process !== "undefined" && process.version ? process.version : "webcrypto",
|
|
674
|
+
claim_input_hash: await sha256Hex(claim),
|
|
675
|
+
verdict,
|
|
676
|
+
verdict_detail: matched === 0 ? "no 'A op B = C' arithmetic claim found \u2014 nothing certified" : res.pass ? `${matched} arithmetic claim(s) re-computed and match` : res.issues.map((i) => i.message).join("; "),
|
|
677
|
+
certifies: verdict === "PASS" ? "arithmetic_correct" : verdict === "FAIL" ? "arithmetic_incorrect" : "nothing",
|
|
678
|
+
does_not_certify: "any reasoning, fact, or step beyond the literal 'A op B = C' arithmetic re-check",
|
|
679
|
+
oracle_ts: Math.floor(Date.now() / 1e3)
|
|
680
|
+
};
|
|
681
|
+
}
|
|
682
|
+
async function verifyReasoningCertificate(cert, claimText) {
|
|
683
|
+
const reasons = [];
|
|
684
|
+
if (cert.tier !== "arithmetic") {
|
|
685
|
+
reasons.push(`offline re-check for tier '${cert.tier}' is not implemented in Slice 0 (arithmetic only)`);
|
|
686
|
+
return { ok: false, reasons };
|
|
687
|
+
}
|
|
688
|
+
const claim = String(claimText ?? "");
|
|
689
|
+
const hashOk = await sha256Hex(claim) === cert.claim_input_hash;
|
|
690
|
+
if (!hashOk) reasons.push("claim_input_hash does not match the provided claim text");
|
|
691
|
+
const recomputed = await certifyArithmetic(claim);
|
|
692
|
+
const verdictOk = recomputed.verdict === cert.verdict;
|
|
693
|
+
if (!verdictOk) reasons.push(`re-computed verdict ${recomputed.verdict} != certificate ${cert.verdict}`);
|
|
694
|
+
return { ok: hashOk && verdictOk, reasons };
|
|
695
|
+
}
|
|
696
|
+
var pExecFile = promisify(execFile);
|
|
697
|
+
function resolveInside(root, p) {
|
|
698
|
+
const abs = isAbsolute(p) ? p : resolve(root, p);
|
|
699
|
+
const rel = relative(root, abs);
|
|
700
|
+
if (rel === ".." || rel.startsWith(`..${sep}`) || isAbsolute(rel)) {
|
|
701
|
+
throw new Error(`path escapes session root: ${p}`);
|
|
702
|
+
}
|
|
703
|
+
return abs;
|
|
704
|
+
}
|
|
705
|
+
var LocalCloudSession = class {
|
|
706
|
+
id;
|
|
707
|
+
root;
|
|
708
|
+
disposed = false;
|
|
709
|
+
constructor(id, root) {
|
|
710
|
+
this.id = id;
|
|
711
|
+
this.root = root;
|
|
712
|
+
}
|
|
713
|
+
async exec(command, options = {}) {
|
|
714
|
+
if (this.disposed) throw new Error("session disposed");
|
|
715
|
+
const cwd = options.cwd ? resolveInside(this.root, options.cwd) : this.root;
|
|
716
|
+
try {
|
|
717
|
+
const { stdout, stderr } = await pExecFile("/bin/sh", ["-c", command], {
|
|
718
|
+
cwd,
|
|
719
|
+
timeout: options.timeoutMs ?? 12e4,
|
|
720
|
+
env: { ...process.env, ...options.env ?? {} },
|
|
721
|
+
maxBuffer: 64 * 1024 * 1024
|
|
722
|
+
});
|
|
723
|
+
return { stdout: stdout.toString(), stderr: stderr.toString(), exitCode: 0 };
|
|
724
|
+
} catch (e) {
|
|
725
|
+
const err = e;
|
|
726
|
+
const exitCode = typeof err.code === "number" ? err.code : err.killed ? 124 : 1;
|
|
727
|
+
return {
|
|
728
|
+
stdout: (err.stdout ?? "").toString(),
|
|
729
|
+
stderr: (err.stderr ?? err.message ?? String(e)).toString(),
|
|
730
|
+
exitCode
|
|
731
|
+
};
|
|
732
|
+
}
|
|
733
|
+
}
|
|
734
|
+
async readFile(path) {
|
|
735
|
+
if (this.disposed) throw new Error("session disposed");
|
|
736
|
+
return readFile(resolveInside(this.root, path), "utf8");
|
|
737
|
+
}
|
|
738
|
+
async writeFile(path, content) {
|
|
739
|
+
if (this.disposed) throw new Error("session disposed");
|
|
740
|
+
const abs = resolveInside(this.root, path);
|
|
741
|
+
await mkdir(dirname(abs), { recursive: true });
|
|
742
|
+
await writeFile(abs, content, "utf8");
|
|
743
|
+
}
|
|
744
|
+
async dispose() {
|
|
745
|
+
if (this.disposed) return;
|
|
746
|
+
this.disposed = true;
|
|
747
|
+
await rm(this.root, { recursive: true, force: true });
|
|
748
|
+
}
|
|
749
|
+
};
|
|
750
|
+
var LocalCloudSessionProvider = class {
|
|
751
|
+
async provision() {
|
|
752
|
+
const id = randomUUID();
|
|
753
|
+
const root = await mkdtemp(join(tmpdir(), `theron-session-${id.slice(0, 8)}-`));
|
|
754
|
+
return new LocalCloudSession(id, root);
|
|
755
|
+
}
|
|
756
|
+
};
|
|
757
|
+
|
|
414
758
|
// src/runtime/index.ts
|
|
415
759
|
var Runner = class {
|
|
416
760
|
model;
|
|
@@ -448,8 +792,9 @@ var Runner = class {
|
|
|
448
792
|
* 4. Run any registered verifier kernels on the final output
|
|
449
793
|
* 5. Return the AgentResult
|
|
450
794
|
*/
|
|
451
|
-
async run(agent, query) {
|
|
795
|
+
async run(agent, query, opts) {
|
|
452
796
|
const startedAt = Date.now();
|
|
797
|
+
const signal = opts?.signal;
|
|
453
798
|
this.emit({ type: "agent_start", agent: agent.name, query });
|
|
454
799
|
const messages = [
|
|
455
800
|
{ role: "system", content: agent.instruction.system }
|
|
@@ -462,49 +807,84 @@ var Runner = class {
|
|
|
462
807
|
const toolCalls = [];
|
|
463
808
|
let tokensIn = 0;
|
|
464
809
|
let tokensOut = 0;
|
|
810
|
+
let costUsd = 0;
|
|
465
811
|
let finalOutput = "";
|
|
812
|
+
let completed = false;
|
|
813
|
+
let aborted = false;
|
|
466
814
|
for (let turn = 0; turn < agent.max_turns; turn++) {
|
|
815
|
+
if (signal?.aborted) {
|
|
816
|
+
aborted = true;
|
|
817
|
+
this.emit({ type: "aborted", agent: agent.name });
|
|
818
|
+
break;
|
|
819
|
+
}
|
|
467
820
|
const response = await this.model.chat({
|
|
468
821
|
model: agent.model ?? this.default_model,
|
|
469
822
|
messages,
|
|
470
823
|
tools: agent.toolSchemas(),
|
|
471
|
-
onDelta: (delta) => this.emit({ type: "agent_thinking", agent: agent.name, delta })
|
|
824
|
+
onDelta: (delta) => this.emit({ type: "agent_thinking", agent: agent.name, delta }),
|
|
825
|
+
signal
|
|
472
826
|
});
|
|
473
827
|
tokensIn += response.tokens.input;
|
|
474
828
|
tokensOut += response.tokens.output;
|
|
829
|
+
costUsd += response.cost_usd ?? 0;
|
|
475
830
|
if (response.tool_calls && response.tool_calls.length > 0) {
|
|
476
831
|
messages.push({ role: "assistant", content: response.content });
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
agent: agent.name,
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
832
|
+
const calls = response.tool_calls;
|
|
833
|
+
const results = await Promise.all(
|
|
834
|
+
calls.map(async (call) => {
|
|
835
|
+
const subAgent = agent.findSubAgent(call.name);
|
|
836
|
+
if (subAgent) {
|
|
837
|
+
this.emit({ type: "tool_call_start", agent: agent.name, tool: call.name, input: call.input });
|
|
838
|
+
const t02 = Date.now();
|
|
839
|
+
try {
|
|
840
|
+
const task = typeof call.input?.task === "string" ? call.input.task : JSON.stringify(call.input);
|
|
841
|
+
const sub = await this.run(subAgent, task, { signal });
|
|
842
|
+
const ms = Date.now() - t02;
|
|
843
|
+
this.emit({ type: "tool_call_done", agent: agent.name, tool: call.name, output: sub.output, ms });
|
|
844
|
+
return { call, output: sub.output, content: sub.output, ok: true };
|
|
845
|
+
} catch (err) {
|
|
846
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
847
|
+
this.emit({ type: "error", agent: agent.name, message: `Sub-agent ${subAgent.name} threw: ${msg}` });
|
|
848
|
+
return { call, content: `error: ${msg}`, ok: false };
|
|
849
|
+
}
|
|
850
|
+
}
|
|
851
|
+
const tool = agent.tools.find((t) => t.schema.name === call.name);
|
|
852
|
+
if (!tool) {
|
|
853
|
+
this.emit({
|
|
854
|
+
type: "error",
|
|
855
|
+
agent: agent.name,
|
|
856
|
+
message: `Model called unknown tool: ${call.name}`
|
|
857
|
+
});
|
|
858
|
+
return { call, content: `error: unknown tool ${call.name}`, ok: false };
|
|
859
|
+
}
|
|
860
|
+
this.emit({ type: "tool_call_start", agent: agent.name, tool: call.name, input: call.input });
|
|
861
|
+
const t0 = Date.now();
|
|
862
|
+
try {
|
|
863
|
+
const output = await tool.execute(call.input, this.tool_context);
|
|
864
|
+
const ms = Date.now() - t0;
|
|
865
|
+
this.emit({ type: "tool_call_done", agent: agent.name, tool: call.name, output, ms });
|
|
866
|
+
return { call, output, content: JSON.stringify(output), ok: true };
|
|
867
|
+
} catch (err) {
|
|
868
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
869
|
+
this.emit({ type: "error", agent: agent.name, message: `Tool ${call.name} threw: ${msg}` });
|
|
870
|
+
return { call, content: `error: ${msg}`, ok: false };
|
|
871
|
+
}
|
|
872
|
+
})
|
|
873
|
+
);
|
|
874
|
+
for (const r of results) {
|
|
875
|
+
if (r.ok) toolCalls.push({ name: r.call.name, input: r.call.input, output: r.output });
|
|
876
|
+
messages.push({ role: "tool", content: r.content });
|
|
501
877
|
}
|
|
502
878
|
continue;
|
|
503
879
|
}
|
|
504
880
|
finalOutput = response.content;
|
|
505
881
|
messages.push({ role: "assistant", content: finalOutput });
|
|
882
|
+
completed = true;
|
|
506
883
|
break;
|
|
507
884
|
}
|
|
885
|
+
if (!completed && !aborted) {
|
|
886
|
+
this.emit({ type: "max_turns_exhausted", agent: agent.name, turns: agent.max_turns });
|
|
887
|
+
}
|
|
508
888
|
this.emit({ type: "agent_output", agent: agent.name, output: finalOutput });
|
|
509
889
|
const verifier_results = [];
|
|
510
890
|
for (const v of agent.verifiers) {
|
|
@@ -527,8 +907,8 @@ var Runner = class {
|
|
|
527
907
|
tool_calls: toolCalls,
|
|
528
908
|
verifier_results,
|
|
529
909
|
tokens_used: { input: tokensIn, output: tokensOut },
|
|
530
|
-
cost_usd:
|
|
531
|
-
// adapter-
|
|
910
|
+
cost_usd: costUsd,
|
|
911
|
+
// summed from adapter-reported per-call cost (0 if the adapter doesn't report it)
|
|
532
912
|
latency_ms
|
|
533
913
|
};
|
|
534
914
|
}
|
|
@@ -538,17 +918,18 @@ var Runner = class {
|
|
|
538
918
|
* Fan out to all specialists in parallel (with timeout), gather outputs,
|
|
539
919
|
* run council-level verifier kernels on each, and reconcile.
|
|
540
920
|
*/
|
|
541
|
-
async runCouncil(council, query) {
|
|
921
|
+
async runCouncil(council, query, opts) {
|
|
542
922
|
const startedAt = Date.now();
|
|
923
|
+
const signal = opts?.signal;
|
|
543
924
|
this.emit({ type: "council_start", council: council.name, query });
|
|
544
925
|
const withTimeout = (p, ms) => Promise.race([
|
|
545
926
|
p,
|
|
546
|
-
new Promise((
|
|
927
|
+
new Promise((resolve2) => setTimeout(() => resolve2(null), ms))
|
|
547
928
|
]);
|
|
548
929
|
const specialistResults = await Promise.all(
|
|
549
930
|
council.specialists.map(async (spec) => {
|
|
550
931
|
try {
|
|
551
|
-
const result = await withTimeout(this.run(spec, query), council.specialist_timeout_ms);
|
|
932
|
+
const result = await withTimeout(this.run(spec, query, { signal }), council.specialist_timeout_ms);
|
|
552
933
|
if (result === null) {
|
|
553
934
|
this.emit({
|
|
554
935
|
type: "error",
|
|
@@ -574,8 +955,11 @@ var Runner = class {
|
|
|
574
955
|
const out = {
|
|
575
956
|
specialist: spec.name,
|
|
576
957
|
output: result.output,
|
|
577
|
-
claims
|
|
578
|
-
//
|
|
958
|
+
// Extract claims if the council supplies an extractor; otherwise the
|
|
959
|
+
// reconciler is responsible (the default deterministic reconciler
|
|
960
|
+
// votes over these claims, so a council that wants automatic
|
|
961
|
+
// ratification should set `claimExtractor`).
|
|
962
|
+
claims: council.claimExtractor ? council.claimExtractor(result.output) : [],
|
|
579
963
|
// AgentResult.verifier_results widens issues to unknown[]; at the
|
|
580
964
|
// runtime layer we know every entry came from a Verifier.check()
|
|
581
965
|
// call (which produces VerifierIssue[]), so the cast is sound.
|
|
@@ -618,7 +1002,7 @@ var MCP_PROTOCOL_VERSION = "2024-11-05";
|
|
|
618
1002
|
var DEFAULT_TIMEOUT_MS = 12e3;
|
|
619
1003
|
var MCPClient = class {
|
|
620
1004
|
config;
|
|
621
|
-
|
|
1005
|
+
initPromise = null;
|
|
622
1006
|
toolCache = null;
|
|
623
1007
|
constructor(config) {
|
|
624
1008
|
if (!/^[a-z0-9_-]+$/.test(config.slug)) {
|
|
@@ -684,7 +1068,17 @@ var MCPClient = class {
|
|
|
684
1068
|
};
|
|
685
1069
|
}
|
|
686
1070
|
async ensureInitialized(signal) {
|
|
687
|
-
if (this.
|
|
1071
|
+
if (!this.initPromise) {
|
|
1072
|
+
this.initPromise = this.doInitialize(signal);
|
|
1073
|
+
}
|
|
1074
|
+
try {
|
|
1075
|
+
await this.initPromise;
|
|
1076
|
+
} catch (err) {
|
|
1077
|
+
this.initPromise = null;
|
|
1078
|
+
throw err;
|
|
1079
|
+
}
|
|
1080
|
+
}
|
|
1081
|
+
async doInitialize(signal) {
|
|
688
1082
|
await this.rpc(
|
|
689
1083
|
"initialize",
|
|
690
1084
|
{
|
|
@@ -695,7 +1089,6 @@ var MCPClient = class {
|
|
|
695
1089
|
signal
|
|
696
1090
|
);
|
|
697
1091
|
this.rpc("notifications/initialized", {}, signal).catch(() => void 0);
|
|
698
|
-
this.initialized = true;
|
|
699
1092
|
}
|
|
700
1093
|
async rpc(method, params, externalSignal) {
|
|
701
1094
|
const ac = new AbortController();
|
|
@@ -709,9 +1102,9 @@ var MCPClient = class {
|
|
|
709
1102
|
}
|
|
710
1103
|
const body = {
|
|
711
1104
|
jsonrpc: "2.0",
|
|
712
|
-
id: Date.now() + Math.floor(Math.random() * 1e3),
|
|
713
1105
|
method,
|
|
714
|
-
params
|
|
1106
|
+
params,
|
|
1107
|
+
...method.startsWith("notifications/") ? {} : { id: Date.now() + Math.floor(Math.random() * 1e3) }
|
|
715
1108
|
};
|
|
716
1109
|
try {
|
|
717
1110
|
const r = await fetch(this.config.url, {
|
|
@@ -734,11 +1127,19 @@ var MCPClient = class {
|
|
|
734
1127
|
const ct = r.headers.get("content-type") || "";
|
|
735
1128
|
if (ct.includes("text/event-stream")) {
|
|
736
1129
|
const text = await r.text();
|
|
737
|
-
const
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
1130
|
+
for (const ev of text.split(/\n\n/)) {
|
|
1131
|
+
const payload = ev.split(/\r?\n/).filter((l) => l.startsWith("data:")).map((l) => l.slice(5).replace(/^ /, "")).join("\n").trim();
|
|
1132
|
+
if (!payload || payload === "[DONE]") continue;
|
|
1133
|
+
let env2;
|
|
1134
|
+
try {
|
|
1135
|
+
env2 = JSON.parse(payload);
|
|
1136
|
+
} catch {
|
|
1137
|
+
continue;
|
|
1138
|
+
}
|
|
1139
|
+
if (env2.error) throw new Error(`mcp error: ${env2.error.message}`);
|
|
1140
|
+
return env2.result;
|
|
1141
|
+
}
|
|
1142
|
+
throw new Error("mcp sse stream had no data event");
|
|
742
1143
|
}
|
|
743
1144
|
const env = await r.json();
|
|
744
1145
|
if (env.error) throw new Error(`mcp error: ${env.error.message}`);
|
|
@@ -797,6 +1198,7 @@ function theronAdapter(opts = {}) {
|
|
|
797
1198
|
let inputTokens = 0;
|
|
798
1199
|
let outputTokens = 0;
|
|
799
1200
|
let buf = "";
|
|
1201
|
+
const toolAcc = {};
|
|
800
1202
|
for (; ; ) {
|
|
801
1203
|
const { value, done } = await reader.read();
|
|
802
1204
|
if (done) break;
|
|
@@ -809,11 +1211,20 @@ function theronAdapter(opts = {}) {
|
|
|
809
1211
|
if (!data || data === "[DONE]") continue;
|
|
810
1212
|
try {
|
|
811
1213
|
const json2 = JSON.parse(data);
|
|
812
|
-
const
|
|
1214
|
+
const d = json2.choices?.[0]?.delta;
|
|
1215
|
+
const delta = d?.content;
|
|
813
1216
|
if (delta) {
|
|
814
1217
|
onDelta(delta);
|
|
815
1218
|
content += delta;
|
|
816
1219
|
}
|
|
1220
|
+
if (Array.isArray(d?.tool_calls)) {
|
|
1221
|
+
for (const tc of d.tool_calls) {
|
|
1222
|
+
const i = typeof tc.index === "number" ? tc.index : 0;
|
|
1223
|
+
toolAcc[i] ??= { name: "", args: "" };
|
|
1224
|
+
if (tc.function?.name) toolAcc[i].name = tc.function.name;
|
|
1225
|
+
if (tc.function?.arguments) toolAcc[i].args += tc.function.arguments;
|
|
1226
|
+
}
|
|
1227
|
+
}
|
|
817
1228
|
if (json2.usage) {
|
|
818
1229
|
inputTokens = json2.usage.prompt_tokens ?? inputTokens;
|
|
819
1230
|
outputTokens = json2.usage.completion_tokens ?? outputTokens;
|
|
@@ -822,7 +1233,8 @@ function theronAdapter(opts = {}) {
|
|
|
822
1233
|
}
|
|
823
1234
|
}
|
|
824
1235
|
}
|
|
825
|
-
|
|
1236
|
+
const tool_calls2 = Object.keys(toolAcc).length ? Object.values(toolAcc).map((t) => ({ name: t.name, input: safeJson(t.args) })) : void 0;
|
|
1237
|
+
return { content, tool_calls: tool_calls2, tokens: { input: inputTokens, output: outputTokens } };
|
|
826
1238
|
}
|
|
827
1239
|
const json = await res.json();
|
|
828
1240
|
const msg = json.choices?.[0]?.message ?? { content: "" };
|
|
@@ -874,7 +1286,7 @@ var ReceiptEmitter = class {
|
|
|
874
1286
|
output: input.output,
|
|
875
1287
|
...input.metadata !== void 0 ? { metadata: input.metadata } : {}
|
|
876
1288
|
};
|
|
877
|
-
const content_hash = await
|
|
1289
|
+
const content_hash = await sha256Hex2(canonicalize(payload));
|
|
878
1290
|
let receipt = {
|
|
879
1291
|
v: "stoa.receipt.v1",
|
|
880
1292
|
id: ulid(),
|
|
@@ -964,7 +1376,7 @@ function canonicalize(value) {
|
|
|
964
1376
|
(k) => JSON.stringify(k) + ":" + canonicalize(value[k])
|
|
965
1377
|
).join(",") + "}";
|
|
966
1378
|
}
|
|
967
|
-
async function
|
|
1379
|
+
async function sha256Hex2(input) {
|
|
968
1380
|
const data = new TextEncoder().encode(input);
|
|
969
1381
|
const buf = await globalThis.crypto.subtle.digest("SHA-256", data);
|
|
970
1382
|
const bytes = new Uint8Array(buf);
|
|
@@ -1058,8 +1470,9 @@ async function compactHistory(opts) {
|
|
|
1058
1470
|
const older = msgs.slice(0, msgs.length - keepRecent);
|
|
1059
1471
|
const recent = msgs.slice(msgs.length - keepRecent);
|
|
1060
1472
|
const summary = String(await opts.summarize(older));
|
|
1473
|
+
const summaryRole = opts.summaryRole ?? "system";
|
|
1061
1474
|
return {
|
|
1062
|
-
messages: [{ role:
|
|
1475
|
+
messages: [{ role: summaryRole, content: `${SUMMARY_PREFIX}
|
|
1063
1476
|
${summary}` }, ...recent],
|
|
1064
1477
|
compacted: true,
|
|
1065
1478
|
summary,
|
|
@@ -1239,6 +1652,6 @@ async function measureLift(opts) {
|
|
|
1239
1652
|
}
|
|
1240
1653
|
|
|
1241
1654
|
// src/index.ts
|
|
1242
|
-
var VERSION = "0.3.
|
|
1655
|
+
var VERSION = "0.3.1";
|
|
1243
1656
|
|
|
1244
|
-
export { Agent, Council, InMemoryReceiptSink, InMemoryStore, LOCAL_TOOL_NAMES, LOCAL_TOOL_PARAMETERS, MCPClient, MUTATING_LOCAL_TOOLS, Memory, ReceiptEmitter, Runner, Session, VERSION, VerifierKernels, allOf, anyOf, bestOfN, boundWorkingSet, buildLocalToolSchemas, chainOfVerification, collectMcpTools, compactHistory, costUsdAtLeast, defineTool, defineVerifier, fileReceiptSink, httpReceiptSink, measureLift, mixtureOfAgents, reflexion, runImprovementCycle, runUntil, selfConsistency, selfRefine, stepCountIs, theron, theronAdapter, treeOfThoughts, verifiedRatchet, verifierSatisfied };
|
|
1657
|
+
export { Agent, Council, InMemoryReceiptSink, InMemoryStore, LOCAL_TOOL_NAMES, LOCAL_TOOL_PARAMETERS, LocalCloudSession, LocalCloudSessionProvider, MCPClient, MUTATING_LOCAL_TOOLS, Memory, ReceiptEmitter, Runner, Session, VERSION, VerifierKernels, allOf, anyOf, bestOfN, boundWorkingSet, buildLocalToolSchemas, certifyArithmetic, chainOfVerification, collectMcpTools, compactHistory, costUsdAtLeast, defineTool, defineVerifier, fileReceiptSink, httpReceiptSink, loadAllMarkdownAgents, loadAllMarkdownSkills, loadMarkdownAgents, loadMarkdownSkills, measureLift, mixtureOfAgents, parseMarkdownAgent, parseMarkdownSkill, reflexion, runImprovementCycle, runUntil, selfConsistency, selfRefine, sentenceClaimExtractor, stepCountIs, subAgentToolName, theron, theronAdapter, treeOfThoughts, verifiedRatchet, verifierSatisfied, verifyReasoningCertificate };
|