agentf 0.4.7 → 0.5.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.
- checksums.yaml +4 -4
- data/lib/agentf/agents/architect.rb +4 -0
- data/lib/agentf/agents/base.rb +29 -1
- data/lib/agentf/agents/debugger.rb +31 -8
- data/lib/agentf/agents/designer.rb +18 -7
- data/lib/agentf/agents/documenter.rb +6 -0
- data/lib/agentf/agents/explorer.rb +30 -11
- data/lib/agentf/agents/reviewer.rb +5 -0
- data/lib/agentf/agents/security.rb +24 -14
- data/lib/agentf/agents/specialist.rb +31 -17
- data/lib/agentf/agents/tester.rb +46 -7
- data/lib/agentf/cli/agent.rb +95 -0
- data/lib/agentf/cli/eval.rb +203 -0
- data/lib/agentf/cli/install.rb +7 -0
- data/lib/agentf/cli/memory.rb +82 -30
- data/lib/agentf/cli/router.rb +15 -3
- data/lib/agentf/cli/update.rb +9 -2
- data/lib/agentf/commands/memory_reviewer.rb +10 -2
- data/lib/agentf/commands/metrics.rb +16 -14
- data/lib/agentf/commands/registry.rb +28 -0
- data/lib/agentf/evals/report.rb +134 -0
- data/lib/agentf/evals/runner.rb +771 -0
- data/lib/agentf/evals/scenario.rb +211 -0
- data/lib/agentf/installer.rb +486 -348
- data/lib/agentf/mcp/server.rb +291 -49
- data/lib/agentf/memory.rb +46 -53
- data/lib/agentf/service/providers.rb +10 -62
- data/lib/agentf/version.rb +1 -1
- data/lib/agentf/workflow_engine.rb +204 -73
- data/lib/agentf.rb +9 -3
- metadata +8 -3
- data/lib/agentf/packs.rb +0 -74
data/lib/agentf/installer.rb
CHANGED
|
@@ -46,12 +46,13 @@ module Agentf
|
|
|
46
46
|
}
|
|
47
47
|
}.freeze
|
|
48
48
|
|
|
49
|
-
def initialize(global_root: Dir.home, local_root: Dir.pwd, dry_run: false, verbose: false, install_deps: true)
|
|
49
|
+
def initialize(global_root: Dir.home, local_root: Dir.pwd, dry_run: false, verbose: false, install_deps: true, opencode_runtime: "mcp")
|
|
50
50
|
@global_root = global_root
|
|
51
51
|
@local_root = local_root
|
|
52
52
|
@dry_run = dry_run
|
|
53
53
|
@verbose = verbose
|
|
54
54
|
@install_deps = install_deps
|
|
55
|
+
@opencode_runtime = opencode_runtime.to_s
|
|
55
56
|
end
|
|
56
57
|
|
|
57
58
|
def install(
|
|
@@ -86,7 +87,7 @@ module Agentf
|
|
|
86
87
|
end
|
|
87
88
|
|
|
88
89
|
# Optionally install dependencies for opencode helper package.json
|
|
89
|
-
if provider.to_s == "opencode" && @install_deps
|
|
90
|
+
if provider.to_s == "opencode" && @install_deps && opencode_plugin_runtime?
|
|
90
91
|
roots.each do |root|
|
|
91
92
|
package_json_path = File.join(root, ".opencode/package.json")
|
|
92
93
|
if @dry_run
|
|
@@ -171,23 +172,33 @@ module Agentf
|
|
|
171
172
|
File.join(root, ".opencode/agents/agentf-orchestrator.md"),
|
|
172
173
|
render_workflow_engine_manifest
|
|
173
174
|
)
|
|
174
|
-
writes << write_manifest(
|
|
175
|
-
File.join(root, ".opencode/plugins/agentf-plugin.ts"),
|
|
176
|
-
render_opencode_plugin
|
|
177
|
-
)
|
|
178
|
-
writes << write_manifest(
|
|
179
|
-
File.join(root, ".opencode/tsconfig.json"),
|
|
180
|
-
render_opencode_tsconfig
|
|
181
|
-
)
|
|
182
|
-
writes << write_package_json(root)
|
|
183
175
|
writes << write_manifest(
|
|
184
176
|
File.join(root, ".opencode/memory/agentf-redis-schema.md"),
|
|
185
177
|
render_opencode_memory_schema
|
|
186
178
|
)
|
|
187
179
|
writes << write_opencode_json(root)
|
|
180
|
+
if opencode_plugin_runtime?
|
|
181
|
+
writes << write_manifest(
|
|
182
|
+
File.join(root, ".opencode/plugins/opencode-plugin.d.ts"),
|
|
183
|
+
render_opencode_plugin
|
|
184
|
+
)
|
|
185
|
+
writes << write_manifest(
|
|
186
|
+
File.join(root, ".opencode/plugins/agentf-plugin.ts"),
|
|
187
|
+
render_agentf_plugin
|
|
188
|
+
)
|
|
189
|
+
writes << write_manifest(
|
|
190
|
+
File.join(root, ".opencode/tsconfig.json"),
|
|
191
|
+
render_opencode_tsconfig
|
|
192
|
+
)
|
|
193
|
+
writes << write_package_json(root)
|
|
194
|
+
end
|
|
188
195
|
writes
|
|
189
196
|
end
|
|
190
197
|
|
|
198
|
+
def opencode_plugin_runtime?
|
|
199
|
+
@opencode_runtime == "plugin"
|
|
200
|
+
end
|
|
201
|
+
|
|
191
202
|
def discover_agents
|
|
192
203
|
Agentf::Agents.constants
|
|
193
204
|
.map { |const| Agentf::Agents.const_get(const) }
|
|
@@ -223,46 +234,42 @@ module Agentf
|
|
|
223
234
|
end
|
|
224
235
|
|
|
225
236
|
def render_agent_manifest(klass, provider:)
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
"memory" => klass.memory_concepts,
|
|
231
|
-
"policy" => klass.policy_boundaries
|
|
232
|
-
}
|
|
237
|
+
# Emit a minimal, stable manifest that acts as a pointer to the runtime
|
|
238
|
+
# tool implemented by the plugin/CLI. Keep filename and `name` stable so
|
|
239
|
+
# upgrades remain compatible with existing installs.
|
|
240
|
+
tool_name = agent_identifier(klass)
|
|
233
241
|
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
#{klass.description}
|
|
242
|
+
# Build a short policy summary (guard nils and limit length)
|
|
243
|
+
pb = klass.respond_to?(:policy_boundaries) ? klass.policy_boundaries || {} : {}
|
|
244
|
+
always = Array(pb["always"]).join("; ")
|
|
245
|
+
ask_first = Array(pb["ask_first"]).join("; ")
|
|
246
|
+
never = Array(pb["never"]).join("; ")
|
|
240
247
|
|
|
241
|
-
|
|
242
|
-
|
|
248
|
+
parts = []
|
|
249
|
+
parts << "Always: #{always}" unless always.to_s.strip.empty?
|
|
250
|
+
parts << "Ask first: #{ask_first}" unless ask_first.to_s.strip.empty?
|
|
251
|
+
parts << "Never: #{never}" unless never.to_s.strip.empty?
|
|
252
|
+
policy_summary = parts.join(" | ")
|
|
243
253
|
|
|
244
|
-
|
|
245
|
-
#{Array(klass.deliverables).map { |item| "- #{item}" }.join("\n")}
|
|
254
|
+
description = klass.respond_to?(:description) ? klass.description.to_s.strip : ""
|
|
246
255
|
|
|
247
|
-
|
|
248
|
-
|
|
256
|
+
<<~MARKDOWN
|
|
257
|
+
---
|
|
258
|
+
name: #{tool_name}
|
|
259
|
+
description: #{description}
|
|
260
|
+
---
|
|
261
|
+
This manifest is a thin pointer. All runtime logic lives in the `#{tool_name}` tool.
|
|
249
262
|
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
- Policy: #{klass.memory_concepts["policy"]}
|
|
263
|
+
IMPORTANT: Use the `#{tool_name}` tool for any filesystem, codebase, or memory actions.
|
|
264
|
+
The manifest contains only routing and a small policy summary — the tool is the
|
|
265
|
+
authoritative implementation.
|
|
254
266
|
|
|
255
|
-
|
|
256
|
-
|
|
267
|
+
If the tool returns `confirmation_required: true`, ask the user whether to continue.
|
|
268
|
+
If they approve, rerun the same tool with `confirmedWrite=confirmed`. If they decline,
|
|
269
|
+
do not retry the write.
|
|
257
270
|
|
|
258
|
-
|
|
259
|
-
- Always: #{Array(klass.policy_boundaries["always"]).join("; ")}
|
|
260
|
-
- Ask first: #{Array(klass.policy_boundaries["ask_first"]).join("; ")}
|
|
261
|
-
- Never: #{Array(klass.policy_boundaries["never"]).join("; ")}
|
|
262
|
-
- Required inputs: #{Array(klass.policy_boundaries["required_inputs"]).join(", ")}
|
|
263
|
-
- Required outputs: #{Array(klass.policy_boundaries["required_outputs"]).join(", ")}
|
|
271
|
+
Policy Summary: #{policy_summary}
|
|
264
272
|
|
|
265
|
-
#{copilot_mcp_agent_section(provider: provider)}
|
|
266
273
|
MARKDOWN
|
|
267
274
|
end
|
|
268
275
|
|
|
@@ -319,20 +326,19 @@ module Agentf
|
|
|
319
326
|
end
|
|
320
327
|
|
|
321
328
|
def render_command_manifest(manifest, provider:)
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
"name" => command_identifier(manifest.fetch("name")),
|
|
325
|
-
"description" => manifest.fetch("description"),
|
|
326
|
-
"commands" => commands
|
|
327
|
-
}
|
|
329
|
+
cmd_name = command_identifier(manifest.fetch("name"))
|
|
330
|
+
desc = manifest.fetch("description", "").to_s.strip
|
|
328
331
|
|
|
329
332
|
<<~MARKDOWN
|
|
330
|
-
|
|
331
|
-
#
|
|
333
|
+
---
|
|
334
|
+
name: #{cmd_name}
|
|
335
|
+
description: #{desc}
|
|
336
|
+
---
|
|
337
|
+
This is a thin command manifest that routes execution to the `#{cmd_name}` tool.
|
|
332
338
|
|
|
333
|
-
|
|
339
|
+
IMPORTANT: Do not embed runtime logic here. Invoke the `#{cmd_name}` tool to perform
|
|
340
|
+
any codebase or memory operations.
|
|
334
341
|
|
|
335
|
-
#{copilot_mcp_command_section(manifest: manifest, provider: provider)}
|
|
336
342
|
MARKDOWN
|
|
337
343
|
end
|
|
338
344
|
|
|
@@ -417,13 +423,34 @@ module Agentf
|
|
|
417
423
|
end
|
|
418
424
|
|
|
419
425
|
def render_opencode_plugin
|
|
426
|
+
<<~'TYPESCRIPT'
|
|
427
|
+
declare module "@opencode-ai/plugin" {
|
|
428
|
+
export type Plugin = (input: any) => Promise<any>;
|
|
429
|
+
|
|
430
|
+
// Minimal `tool` factory type used by our plugin. Keep very loose to avoid
|
|
431
|
+
// coupling to the full SDK types in this repo.
|
|
432
|
+
export function tool(def: any): any;
|
|
433
|
+
|
|
434
|
+
export const schema: any;
|
|
435
|
+
|
|
436
|
+
export default {} as { tool: typeof tool; schema: any };
|
|
437
|
+
}
|
|
438
|
+
TYPESCRIPT
|
|
439
|
+
end
|
|
440
|
+
|
|
441
|
+
def render_agentf_plugin
|
|
420
442
|
<<~'TYPESCRIPT'
|
|
421
443
|
// tools:
|
|
422
444
|
import { execFile } from "child_process";
|
|
423
445
|
import { promisify } from "util";
|
|
424
|
-
import path from "path";
|
|
425
|
-
|
|
426
|
-
|
|
446
|
+
import * as path from "path";
|
|
447
|
+
// Avoid importing host SDK types directly to reduce coupling during local
|
|
448
|
+
// type-checks. Use a runtime require and loose `any` types here.
|
|
449
|
+
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
|
450
|
+
const _opencode_plugin: any = require("@opencode-ai/plugin");
|
|
451
|
+
const tool = _opencode_plugin.tool;
|
|
452
|
+
type Plugin = any;
|
|
453
|
+
import * as fs from "fs";
|
|
427
454
|
|
|
428
455
|
const execFileAsync = promisify(execFile);
|
|
429
456
|
|
|
@@ -553,317 +580,414 @@ module Agentf
|
|
|
553
580
|
});
|
|
554
581
|
|
|
555
582
|
const text = stdout.toString().trim();
|
|
556
|
-
|
|
583
|
+
if (!text) return {};
|
|
584
|
+
|
|
585
|
+
try {
|
|
586
|
+
return JSON.parse(text);
|
|
587
|
+
} catch (err) {
|
|
588
|
+
// If the CLI returned non-JSON, return a structured error object so callers
|
|
589
|
+
// can surface useful debugging info instead of crashing.
|
|
590
|
+
return { ok: false, _parse_error: String(err), _raw: text };
|
|
591
|
+
}
|
|
557
592
|
}
|
|
558
593
|
|
|
559
|
-
|
|
560
|
-
|
|
594
|
+
// Lightweight frontmatter parser: extract YAML between leading `---` blocks
|
|
595
|
+
function parseFrontmatter(content: string): Record<string, string> {
|
|
596
|
+
const res: Record<string, string> = {};
|
|
597
|
+
const fmStart = content.indexOf("---");
|
|
598
|
+
if (fmStart === -1) return res;
|
|
599
|
+
const rest = content.slice(fmStart + 3);
|
|
600
|
+
const fmEndIdx = rest.indexOf("---");
|
|
601
|
+
if (fmEndIdx === -1) return res;
|
|
602
|
+
const block = rest.slice(0, fmEndIdx).trim();
|
|
603
|
+
const lines = block.split(/\r?\n/);
|
|
604
|
+
for (const line of lines) {
|
|
605
|
+
const m = line.match(/^\s*([A-Za-z0-9_\-]+)\s*:\s*(.+)\s*$/);
|
|
606
|
+
if (!m) continue;
|
|
607
|
+
let key = m[1];
|
|
608
|
+
let value = m[2];
|
|
609
|
+
// strip surrounding quotes
|
|
610
|
+
if ((value.startsWith('"') && value.endsWith('"')) || (value.startsWith("'") && value.endsWith("'"))) {
|
|
611
|
+
value = value.slice(1, -1);
|
|
612
|
+
}
|
|
613
|
+
res[key] = value;
|
|
614
|
+
}
|
|
615
|
+
return res;
|
|
616
|
+
}
|
|
561
617
|
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
618
|
+
export const agentfPlugin = async (input: any) => {
|
|
619
|
+
const workspaceDir = input?.directory || process.env.PWD || process.cwd();
|
|
620
|
+
await ensureAgentfPreflight(workspaceDir);
|
|
621
|
+
|
|
622
|
+
// Build static tools map
|
|
623
|
+
const staticTools: Record<string, ReturnType<typeof tool>> = {
|
|
624
|
+
"agentf-code-glob": tool({
|
|
625
|
+
description: "Find files using project glob patterns via Agentf code CLI.",
|
|
626
|
+
args: {
|
|
627
|
+
pattern: tool.schema.string().describe("Glob pattern, example: lib/**/*.rb"),
|
|
628
|
+
types: tool.schema.array(tool.schema.string()).optional().describe("Optional file extensions"),
|
|
629
|
+
},
|
|
630
|
+
async execute(_args: any, context: any) {
|
|
631
|
+
const commandArgs = [];
|
|
632
|
+
if (_args.types?.length) {
|
|
633
|
+
commandArgs.push(`--types=${_args.types.join(",")}`);
|
|
634
|
+
}
|
|
635
|
+
|
|
636
|
+
return runAgentfCli(context.directory, "code", "glob", [_args.pattern, ...commandArgs]);
|
|
637
|
+
},
|
|
638
|
+
}),
|
|
639
|
+
"agentf-code-grep": tool({
|
|
640
|
+
description: "Search file contents via Agentf code CLI.",
|
|
641
|
+
args: {
|
|
642
|
+
pattern: tool.schema.string().describe("Regex/text to search"),
|
|
643
|
+
filePattern: tool.schema.string().optional().describe("Optional include pattern"),
|
|
644
|
+
context: tool.schema.number().int().min(0).max(20).optional().describe("Context lines"),
|
|
645
|
+
},
|
|
646
|
+
async execute(_args: any, context: any) {
|
|
647
|
+
const commandArgs = [];
|
|
648
|
+
if (_args.filePattern) commandArgs.push(`--file-pattern=${_args.filePattern}`);
|
|
649
|
+
if (Number.isInteger(_args.context)) commandArgs.push(`--context=${_args.context}`);
|
|
650
|
+
|
|
651
|
+
return runAgentfCli(context.directory, "code", "grep", [_args.pattern, ...commandArgs]);
|
|
652
|
+
},
|
|
653
|
+
}),
|
|
654
|
+
"agentf-code-tree": tool({
|
|
655
|
+
description: "Get directory tree data via Agentf code CLI.",
|
|
656
|
+
args: {
|
|
657
|
+
depth: tool.schema.number().int().min(1).max(10).optional().describe("Max traversal depth"),
|
|
658
|
+
},
|
|
659
|
+
async execute(_args: any, context: any) {
|
|
660
|
+
const depth = _args.depth ?? 3;
|
|
661
|
+
return runAgentfCli(context.directory, "code", "tree", [`--depth=${depth}`]);
|
|
662
|
+
},
|
|
663
|
+
}),
|
|
664
|
+
"agentf-code-related-files": tool({
|
|
665
|
+
description: "Find import and related file hints for a target file.",
|
|
666
|
+
args: {
|
|
667
|
+
targetFile: tool.schema.string().describe("Workspace-relative file path"),
|
|
668
|
+
},
|
|
669
|
+
async execute(_args: any, context: any) {
|
|
670
|
+
return runAgentfCli(context.directory, "code", "related", [_args.targetFile]);
|
|
671
|
+
},
|
|
672
|
+
}),
|
|
673
|
+
"agentf-memory-recent": tool({
|
|
674
|
+
description: "Get recent Agentf memories from Redis.",
|
|
675
|
+
args: {
|
|
676
|
+
limit: tool.schema.number().int().min(1).max(100).optional().describe("How many memories to return"),
|
|
677
|
+
},
|
|
678
|
+
async execute(_args: any, context: any) {
|
|
679
|
+
const limit = _args.limit ?? 10;
|
|
680
|
+
return runAgentfCli(context.directory, "memory", "recent", ["-n", String(limit)]);
|
|
681
|
+
},
|
|
682
|
+
}),
|
|
683
|
+
"agentf-memory-search": tool({
|
|
684
|
+
description: "Search Agentf memories by keyword.",
|
|
685
|
+
args: {
|
|
686
|
+
query: tool.schema.string().describe("Search query"),
|
|
687
|
+
limit: tool.schema.number().int().min(1).max(100).optional().describe("How many results to return"),
|
|
688
|
+
},
|
|
689
|
+
async execute(_args: any, context: any) {
|
|
690
|
+
const limit = _args.limit ?? 10;
|
|
691
|
+
return runAgentfCli(context.directory, "memory", "search", [_args.query, "-n", String(limit)]);
|
|
692
|
+
},
|
|
693
|
+
}),
|
|
694
|
+
"agentf-memory-by-tag": tool({
|
|
695
|
+
description: "Get Agentf memories by tag.",
|
|
696
|
+
args: {
|
|
697
|
+
tag: tool.schema.string().describe("Tag to filter by"),
|
|
698
|
+
limit: tool.schema.number().int().min(1).max(100).optional().describe("How many results to return"),
|
|
699
|
+
},
|
|
700
|
+
async execute(_args: any, context: any) {
|
|
701
|
+
const limit = _args.limit ?? 10;
|
|
702
|
+
return runAgentfCli(context.directory, "memory", "by-tag", [_args.tag, "-n", String(limit)]);
|
|
703
|
+
},
|
|
704
|
+
}),
|
|
705
|
+
"agentf-memory-by-agent": tool({
|
|
706
|
+
description: "Get Agentf memories by agent.",
|
|
707
|
+
args: {
|
|
708
|
+
agent: tool.schema.string().describe("Agent name"),
|
|
709
|
+
limit: tool.schema.number().int().min(1).max(100).optional().describe("How many results to return"),
|
|
710
|
+
},
|
|
711
|
+
async execute(_args: any, context: any) {
|
|
712
|
+
const limit = _args.limit ?? 10;
|
|
713
|
+
return runAgentfCli(context.directory, "memory", "by-agent", [_args.agent, "-n", String(limit)]);
|
|
714
|
+
},
|
|
715
|
+
}),
|
|
716
|
+
"agentf-memory-by-type": tool({
|
|
717
|
+
description: "Get Agentf memories by type.",
|
|
718
|
+
args: {
|
|
719
|
+
type: tool.schema.string().describe("Memory type (pitfall|lesson|success|business_intent|feature_intent)"),
|
|
720
|
+
limit: tool.schema.number().int().min(1).max(100).optional().describe("How many results to return"),
|
|
721
|
+
},
|
|
722
|
+
async execute(_args: any, context: any) {
|
|
723
|
+
const limit = _args.limit ?? 10;
|
|
724
|
+
return runAgentfCli(context.directory, "memory", "by-type", [_args.type, "-n", String(limit)]);
|
|
725
|
+
},
|
|
726
|
+
}),
|
|
727
|
+
"agentf-memory-tags": tool({
|
|
728
|
+
description: "List all unique memory tags.",
|
|
729
|
+
args: {},
|
|
730
|
+
async execute(_args: any, context: any) {
|
|
731
|
+
return runAgentfCli(context.directory, "memory", "tags", []);
|
|
732
|
+
},
|
|
733
|
+
}),
|
|
734
|
+
"agentf-memory-pitfalls": tool({
|
|
735
|
+
description: "List pitfall memories.",
|
|
736
|
+
args: { limit: tool.schema.number().int().min(1).max(100).optional() },
|
|
737
|
+
async execute(_args: any, context: any) {
|
|
738
|
+
const limit = _args.limit ?? 10;
|
|
739
|
+
return runAgentfCli(context.directory, "memory", "pitfalls", ["-n", String(limit)]);
|
|
740
|
+
},
|
|
741
|
+
}),
|
|
742
|
+
"agentf-memory-lessons": tool({
|
|
743
|
+
description: "List lesson memories.",
|
|
744
|
+
args: { limit: tool.schema.number().int().min(1).max(100).optional() },
|
|
745
|
+
async execute(_args: any, context: any) {
|
|
746
|
+
const limit = _args.limit ?? 10;
|
|
747
|
+
return runAgentfCli(context.directory, "memory", "lessons", ["-n", String(limit)]);
|
|
748
|
+
},
|
|
749
|
+
}),
|
|
750
|
+
"agentf-memory-successes": tool({
|
|
751
|
+
description: "List success memories.",
|
|
752
|
+
args: { limit: tool.schema.number().int().min(1).max(100).optional() },
|
|
753
|
+
async execute(_args: any, context: any) {
|
|
754
|
+
const limit = _args.limit ?? 10;
|
|
755
|
+
return runAgentfCli(context.directory, "memory", "successes", ["-n", String(limit)]);
|
|
756
|
+
},
|
|
757
|
+
}),
|
|
758
|
+
"agentf-memory-intents": tool({
|
|
759
|
+
description: "List intents (business, feature or both).",
|
|
760
|
+
args: { kind: tool.schema.string().optional(), limit: tool.schema.number().int().min(1).max(100).optional() },
|
|
761
|
+
async execute(_args: any, context: any) {
|
|
762
|
+
const limit = _args.limit ?? 10;
|
|
763
|
+
const kind = _args.kind ? String(_args.kind) : "";
|
|
764
|
+
const cmdArgs = kind ? [kind, "-n", String(limit)] : ["-n", String(limit)];
|
|
765
|
+
return runAgentfCli(context.directory, "memory", "intents", cmdArgs);
|
|
766
|
+
},
|
|
767
|
+
}),
|
|
768
|
+
"agentf-memory-business-intents": tool({
|
|
769
|
+
description: "List business intents.",
|
|
770
|
+
args: { limit: tool.schema.number().int().min(1).max(100).optional() },
|
|
771
|
+
async execute(_args: any, context: any) {
|
|
772
|
+
const limit = _args.limit ?? 10;
|
|
773
|
+
return runAgentfCli(context.directory, "memory", "business-intents", ["-n", String(limit)]);
|
|
774
|
+
},
|
|
775
|
+
}),
|
|
776
|
+
"agentf-memory-feature-intents": tool({
|
|
777
|
+
description: "List feature intents.",
|
|
778
|
+
args: { limit: tool.schema.number().int().min(1).max(100).optional() },
|
|
779
|
+
async execute(_args: any, context: any) {
|
|
780
|
+
const limit = _args.limit ?? 10;
|
|
781
|
+
return runAgentfCli(context.directory, "memory", "feature-intents", ["-n", String(limit)]);
|
|
782
|
+
},
|
|
783
|
+
}),
|
|
784
|
+
"agentf-memory-add-business-intent": tool({
|
|
785
|
+
description: "Store a business intent in Redis.",
|
|
786
|
+
args: {
|
|
787
|
+
title: tool.schema.string(),
|
|
788
|
+
description: tool.schema.string(),
|
|
789
|
+
tags: tool.schema.array(tool.schema.string()).optional(),
|
|
790
|
+
constraints: tool.schema.array(tool.schema.string()).optional(),
|
|
791
|
+
priority: tool.schema.number().int().optional(),
|
|
792
|
+
},
|
|
793
|
+
async execute(_args: any, context: any) {
|
|
794
|
+
const commandArgs = [_args.title, _args.description];
|
|
795
|
+
if (_args.tags?.length) commandArgs.push(`--tags=${_args.tags.join(",")}`);
|
|
796
|
+
if (_args.constraints?.length) commandArgs.push(`--constraints=${_args.constraints.join(";")}`);
|
|
797
|
+
if (Number.isInteger(_args.priority)) commandArgs.push(`--priority=${String(_args.priority)}`);
|
|
798
|
+
return runAgentfCli(context.directory, "memory", "add-business-intent", commandArgs);
|
|
799
|
+
},
|
|
800
|
+
}),
|
|
801
|
+
"agentf-memory-add-feature-intent": tool({
|
|
802
|
+
description: "Store a feature intent in Redis.",
|
|
803
|
+
args: {
|
|
804
|
+
title: tool.schema.string(),
|
|
805
|
+
description: tool.schema.string(),
|
|
806
|
+
tags: tool.schema.array(tool.schema.string()).optional(),
|
|
807
|
+
acceptance: tool.schema.array(tool.schema.string()).optional(),
|
|
808
|
+
non_goals: tool.schema.array(tool.schema.string()).optional(),
|
|
809
|
+
related_task_id: tool.schema.string().optional(),
|
|
810
|
+
},
|
|
811
|
+
async execute(_args: any, context: any) {
|
|
812
|
+
const commandArgs = [_args.title, _args.description];
|
|
813
|
+
if (_args.tags?.length) commandArgs.push(`--tags=${_args.tags.join(",")}`);
|
|
814
|
+
if (_args.acceptance?.length) commandArgs.push(`--acceptance=${_args.acceptance.join(";")}`);
|
|
815
|
+
if (_args.non_goals?.length) commandArgs.push(`--non-goals=${_args.non_goals.join(";")}`);
|
|
816
|
+
if (_args.related_task_id) commandArgs.push(`--task=${_args.related_task_id}`);
|
|
817
|
+
return runAgentfCli(context.directory, "memory", "add-feature-intent", commandArgs);
|
|
818
|
+
},
|
|
819
|
+
}),
|
|
820
|
+
"agentf-memory-neighbors": tool({
|
|
821
|
+
description: "Get neighboring memory nodes by edge traversal.",
|
|
822
|
+
args: {
|
|
823
|
+
node_id: tool.schema.string(),
|
|
824
|
+
relation: tool.schema.string().optional(),
|
|
825
|
+
depth: tool.schema.number().int().optional(),
|
|
826
|
+
limit: tool.schema.number().int().optional(),
|
|
827
|
+
},
|
|
828
|
+
async execute(_args: any, context: any) {
|
|
829
|
+
const commandArgs = [_args.node_id];
|
|
830
|
+
if (_args.relation) commandArgs.push(`--relation=${_args.relation}`);
|
|
831
|
+
if (Number.isInteger(_args.depth)) commandArgs.push(`--depth=${String(_args.depth)}`);
|
|
832
|
+
if (Number.isInteger(_args.limit)) commandArgs.push(`-n`, String(_args.limit));
|
|
833
|
+
return runAgentfCli(context.directory, "memory", "neighbors", commandArgs);
|
|
834
|
+
},
|
|
835
|
+
}),
|
|
836
|
+
"agentf-memory-subgraph": tool({
|
|
837
|
+
description: "Build a subgraph from seed ids.",
|
|
838
|
+
args: {
|
|
839
|
+
seed_ids: tool.schema.array(tool.schema.string()),
|
|
840
|
+
relation_filters: tool.schema.array(tool.schema.string()).optional(),
|
|
841
|
+
depth: tool.schema.number().int().optional(),
|
|
842
|
+
limit: tool.schema.number().int().optional(),
|
|
843
|
+
},
|
|
844
|
+
async execute(_args: any, context: any) {
|
|
845
|
+
const seeds = (_args.seed_ids || []).join(",");
|
|
846
|
+
const commandArgs = [seeds];
|
|
847
|
+
if (_args.relation_filters?.length) commandArgs.push(`--relation=${_args.relation_filters.join(",")}`);
|
|
848
|
+
if (Number.isInteger(_args.depth)) commandArgs.push(`--depth=${String(_args.depth)}`);
|
|
849
|
+
if (Number.isInteger(_args.limit)) commandArgs.push(`-n`, String(_args.limit));
|
|
850
|
+
return runAgentfCli(context.directory, "memory", "subgraph", commandArgs);
|
|
851
|
+
},
|
|
852
|
+
}),
|
|
853
|
+
"agentf-memory-add-lesson": tool({
|
|
854
|
+
description: "Store a lesson memory in Redis.",
|
|
855
|
+
args: {
|
|
856
|
+
title: tool.schema.string(),
|
|
857
|
+
description: tool.schema.string(),
|
|
858
|
+
agent: tool.schema.string().optional(),
|
|
859
|
+
tags: tool.schema.array(tool.schema.string()).optional(),
|
|
860
|
+
context: tool.schema.string().optional(),
|
|
861
|
+
},
|
|
862
|
+
async execute(_args: any, context: any) {
|
|
863
|
+
const commandArgs = [_args.title, _args.description];
|
|
864
|
+
if (_args.agent) commandArgs.push(`--agent=${_args.agent}`);
|
|
865
|
+
if (_args.tags?.length) commandArgs.push(`--tags=${_args.tags.join(",")}`);
|
|
866
|
+
if (_args.context) commandArgs.push(`--context=${_args.context}`);
|
|
867
|
+
|
|
868
|
+
return runAgentfCli(context.directory, "memory", "add-lesson", commandArgs);
|
|
869
|
+
},
|
|
870
|
+
}),
|
|
871
|
+
"agentf-memory-add-success": tool({
|
|
872
|
+
description: "Store a success memory in Redis.",
|
|
873
|
+
args: {
|
|
874
|
+
title: tool.schema.string(),
|
|
875
|
+
description: tool.schema.string(),
|
|
876
|
+
agent: tool.schema.string().optional(),
|
|
877
|
+
tags: tool.schema.array(tool.schema.string()).optional(),
|
|
878
|
+
context: tool.schema.string().optional(),
|
|
879
|
+
},
|
|
880
|
+
async execute(_args: any, context: any) {
|
|
881
|
+
const commandArgs = [_args.title, _args.description];
|
|
882
|
+
if (_args.agent) commandArgs.push(`--agent=${_args.agent}`);
|
|
883
|
+
if (_args.tags?.length) commandArgs.push(`--tags=${_args.tags.join(",")}`);
|
|
884
|
+
if (_args.context) commandArgs.push(`--context=${_args.context}`);
|
|
885
|
+
|
|
886
|
+
return runAgentfCli(context.directory, "memory", "add-success", commandArgs);
|
|
887
|
+
},
|
|
888
|
+
}),
|
|
889
|
+
"agentf-memory-add-pitfall": tool({
|
|
890
|
+
description: "Store a pitfall memory in Redis.",
|
|
891
|
+
args: {
|
|
892
|
+
title: tool.schema.string(),
|
|
893
|
+
description: tool.schema.string(),
|
|
894
|
+
agent: tool.schema.string().optional(),
|
|
895
|
+
tags: tool.schema.array(tool.schema.string()).optional(),
|
|
896
|
+
context: tool.schema.string().optional(),
|
|
897
|
+
},
|
|
898
|
+
async execute(_args: any, context: any) {
|
|
899
|
+
const commandArgs = [_args.title, _args.description];
|
|
900
|
+
if (_args.agent) commandArgs.push(`--agent=${_args.agent}`);
|
|
901
|
+
if (_args.tags?.length) commandArgs.push(`--tags=${_args.tags.join(",")}`);
|
|
902
|
+
if (_args.context) commandArgs.push(`--context=${_args.context}`);
|
|
903
|
+
|
|
904
|
+
return runAgentfCli(context.directory, "memory", "add-pitfall", commandArgs);
|
|
905
|
+
},
|
|
906
|
+
}),
|
|
907
|
+
};
|
|
575
908
|
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
}),
|
|
579
|
-
"agentf-code-grep": tool({
|
|
580
|
-
description: "Search file contents via Agentf code CLI.",
|
|
581
|
-
args: {
|
|
582
|
-
pattern: tool.schema.string().describe("Regex/text to search"),
|
|
583
|
-
filePattern: tool.schema.string().optional().describe("Optional include pattern"),
|
|
584
|
-
context: tool.schema.number().int().min(0).max(20).optional().describe("Context lines"),
|
|
585
|
-
},
|
|
586
|
-
async execute(args, context) {
|
|
587
|
-
const commandArgs = [];
|
|
588
|
-
if (args.filePattern) commandArgs.push(`--file-pattern=${args.filePattern}`);
|
|
589
|
-
if (Number.isInteger(args.context)) commandArgs.push(`--context=${args.context}`);
|
|
909
|
+
const agentTools: Record<string, ReturnType<typeof tool>> = {};
|
|
910
|
+
const absDir = path.join(process.cwd(), ".opencode/agents");
|
|
590
911
|
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
async execute(args, context) {
|
|
600
|
-
const depth = args.depth ?? 3;
|
|
601
|
-
return runAgentfCli(context.directory, "code", "tree", [`--depth=${depth}`]);
|
|
602
|
-
},
|
|
603
|
-
}),
|
|
604
|
-
"agentf-code-related-files": tool({
|
|
605
|
-
description: "Find import and related file hints for a target file.",
|
|
606
|
-
args: {
|
|
607
|
-
targetFile: tool.schema.string().describe("Workspace-relative file path"),
|
|
608
|
-
},
|
|
609
|
-
async execute(args, context) {
|
|
610
|
-
return runAgentfCli(context.directory, "code", "related", [args.targetFile]);
|
|
611
|
-
},
|
|
612
|
-
}),
|
|
613
|
-
"agentf-memory-recent": tool({
|
|
614
|
-
description: "Get recent Agentf memories from Redis.",
|
|
615
|
-
args: {
|
|
616
|
-
limit: tool.schema.number().int().min(1).max(100).optional().describe("How many memories to return"),
|
|
617
|
-
},
|
|
618
|
-
async execute(args, context) {
|
|
619
|
-
const limit = args.limit ?? 10;
|
|
620
|
-
return runAgentfCli(context.directory, "memory", "recent", ["-n", String(limit)]);
|
|
621
|
-
},
|
|
622
|
-
}),
|
|
623
|
-
"agentf-memory-search": tool({
|
|
624
|
-
description: "Search Agentf memories by keyword.",
|
|
625
|
-
args: {
|
|
626
|
-
query: tool.schema.string().describe("Search query"),
|
|
627
|
-
limit: tool.schema.number().int().min(1).max(100).optional().describe("How many results to return"),
|
|
628
|
-
},
|
|
629
|
-
async execute(args, context) {
|
|
630
|
-
const limit = args.limit ?? 10;
|
|
631
|
-
return runAgentfCli(context.directory, "memory", "search", [args.query, "-n", String(limit)]);
|
|
632
|
-
},
|
|
633
|
-
}),
|
|
634
|
-
"agentf-memory-by-tag": tool({
|
|
635
|
-
description: "Get Agentf memories by tag.",
|
|
636
|
-
args: {
|
|
637
|
-
tag: tool.schema.string().describe("Tag to filter by"),
|
|
638
|
-
limit: tool.schema.number().int().min(1).max(100).optional().describe("How many results to return"),
|
|
639
|
-
},
|
|
640
|
-
async execute(args, context) {
|
|
641
|
-
const limit = args.limit ?? 10;
|
|
642
|
-
return runAgentfCli(context.directory, "memory", "by-tag", [args.tag, "-n", String(limit)]);
|
|
643
|
-
},
|
|
644
|
-
}),
|
|
645
|
-
"agentf-memory-by-agent": tool({
|
|
646
|
-
description: "Get Agentf memories by agent.",
|
|
647
|
-
args: {
|
|
648
|
-
agent: tool.schema.string().describe("Agent name"),
|
|
649
|
-
limit: tool.schema.number().int().min(1).max(100).optional().describe("How many results to return"),
|
|
650
|
-
},
|
|
651
|
-
async execute(args, context) {
|
|
652
|
-
const limit = args.limit ?? 10;
|
|
653
|
-
return runAgentfCli(context.directory, "memory", "by-agent", [args.agent, "-n", String(limit)]);
|
|
654
|
-
},
|
|
655
|
-
}),
|
|
656
|
-
"agentf-memory-by-type": tool({
|
|
657
|
-
description: "Get Agentf memories by type.",
|
|
658
|
-
args: {
|
|
659
|
-
type: tool.schema.string().describe("Memory type (pitfall|lesson|success|business_intent|feature_intent)"),
|
|
660
|
-
limit: tool.schema.number().int().min(1).max(100).optional().describe("How many results to return"),
|
|
661
|
-
},
|
|
662
|
-
async execute(args, context) {
|
|
663
|
-
const limit = args.limit ?? 10;
|
|
664
|
-
return runAgentfCli(context.directory, "memory", "by-type", [args.type, "-n", String(limit)]);
|
|
665
|
-
},
|
|
666
|
-
}),
|
|
667
|
-
"agentf-memory-tags": tool({
|
|
668
|
-
description: "List all unique memory tags.",
|
|
669
|
-
args: {},
|
|
670
|
-
async execute(_args, context) {
|
|
671
|
-
return runAgentfCli(context.directory, "memory", "tags", []);
|
|
672
|
-
},
|
|
673
|
-
}),
|
|
674
|
-
"agentf-memory-pitfalls": tool({
|
|
675
|
-
description: "List pitfall memories.",
|
|
676
|
-
args: { limit: tool.schema.number().int().min(1).max(100).optional() },
|
|
677
|
-
async execute(args, context) {
|
|
678
|
-
const limit = args.limit ?? 10;
|
|
679
|
-
return runAgentfCli(context.directory, "memory", "pitfalls", ["-n", String(limit)]);
|
|
680
|
-
},
|
|
681
|
-
}),
|
|
682
|
-
"agentf-memory-lessons": tool({
|
|
683
|
-
description: "List lesson memories.",
|
|
684
|
-
args: { limit: tool.schema.number().int().min(1).max(100).optional() },
|
|
685
|
-
async execute(args, context) {
|
|
686
|
-
const limit = args.limit ?? 10;
|
|
687
|
-
return runAgentfCli(context.directory, "memory", "lessons", ["-n", String(limit)]);
|
|
688
|
-
},
|
|
689
|
-
}),
|
|
690
|
-
"agentf-memory-successes": tool({
|
|
691
|
-
description: "List success memories.",
|
|
692
|
-
args: { limit: tool.schema.number().int().min(1).max(100).optional() },
|
|
693
|
-
async execute(args, context) {
|
|
694
|
-
const limit = args.limit ?? 10;
|
|
695
|
-
return runAgentfCli(context.directory, "memory", "successes", ["-n", String(limit)]);
|
|
696
|
-
},
|
|
697
|
-
}),
|
|
698
|
-
"agentf-memory-intents": tool({
|
|
699
|
-
description: "List intents (business, feature or both).",
|
|
700
|
-
args: { kind: tool.schema.string().optional(), limit: tool.schema.number().int().min(1).max(100).optional() },
|
|
701
|
-
async execute(args, context) {
|
|
702
|
-
const limit = args.limit ?? 10;
|
|
703
|
-
const kind = args.kind ? String(args.kind) : "";
|
|
704
|
-
const cmdArgs = kind ? [kind, "-n", String(limit)] : ["-n", String(limit)];
|
|
705
|
-
return runAgentfCli(context.directory, "memory", "intents", cmdArgs);
|
|
706
|
-
},
|
|
707
|
-
}),
|
|
708
|
-
"agentf-memory-business-intents": tool({
|
|
709
|
-
description: "List business intents.",
|
|
710
|
-
args: { limit: tool.schema.number().int().min(1).max(100).optional() },
|
|
711
|
-
async execute(args, context) {
|
|
712
|
-
const limit = args.limit ?? 10;
|
|
713
|
-
return runAgentfCli(context.directory, "memory", "business-intents", ["-n", String(limit)]);
|
|
714
|
-
},
|
|
715
|
-
}),
|
|
716
|
-
"agentf-memory-feature-intents": tool({
|
|
717
|
-
description: "List feature intents.",
|
|
718
|
-
args: { limit: tool.schema.number().int().min(1).max(100).optional() },
|
|
719
|
-
async execute(args, context) {
|
|
720
|
-
const limit = args.limit ?? 10;
|
|
721
|
-
return runAgentfCli(context.directory, "memory", "feature-intents", ["-n", String(limit)]);
|
|
722
|
-
},
|
|
723
|
-
}),
|
|
724
|
-
"agentf-memory-add-business-intent": tool({
|
|
725
|
-
description: "Store a business intent in Redis.",
|
|
726
|
-
args: {
|
|
727
|
-
title: tool.schema.string(),
|
|
728
|
-
description: tool.schema.string(),
|
|
729
|
-
tags: tool.schema.array(tool.schema.string()).optional(),
|
|
730
|
-
constraints: tool.schema.array(tool.schema.string()).optional(),
|
|
731
|
-
priority: tool.schema.number().int().optional(),
|
|
732
|
-
},
|
|
733
|
-
async execute(args, context) {
|
|
734
|
-
const commandArgs = [args.title, args.description];
|
|
735
|
-
if (args.tags?.length) commandArgs.push(`--tags=${args.tags.join(",")}`);
|
|
736
|
-
if (args.constraints?.length) commandArgs.push(`--constraints=${args.constraints.join(";")}`);
|
|
737
|
-
if (Number.isInteger(args.priority)) commandArgs.push(`--priority=${String(args.priority)}`);
|
|
738
|
-
return runAgentfCli(context.directory, "memory", "add-business-intent", commandArgs);
|
|
739
|
-
},
|
|
740
|
-
}),
|
|
741
|
-
"agentf-memory-add-feature-intent": tool({
|
|
742
|
-
description: "Store a feature intent in Redis.",
|
|
743
|
-
args: {
|
|
744
|
-
title: tool.schema.string(),
|
|
745
|
-
description: tool.schema.string(),
|
|
746
|
-
tags: tool.schema.array(tool.schema.string()).optional(),
|
|
747
|
-
acceptance: tool.schema.array(tool.schema.string()).optional(),
|
|
748
|
-
non_goals: tool.schema.array(tool.schema.string()).optional(),
|
|
749
|
-
related_task_id: tool.schema.string().optional(),
|
|
750
|
-
},
|
|
751
|
-
async execute(args, context) {
|
|
752
|
-
const commandArgs = [args.title, args.description];
|
|
753
|
-
if (args.tags?.length) commandArgs.push(`--tags=${args.tags.join(",")}`);
|
|
754
|
-
if (args.acceptance?.length) commandArgs.push(`--acceptance=${args.acceptance.join(";")}`);
|
|
755
|
-
if (args.non_goals?.length) commandArgs.push(`--non-goals=${args.non_goals.join(";")}`);
|
|
756
|
-
if (args.related_task_id) commandArgs.push(`--task=${args.related_task_id}`);
|
|
757
|
-
return runAgentfCli(context.directory, "memory", "add-feature-intent", commandArgs);
|
|
758
|
-
},
|
|
759
|
-
}),
|
|
760
|
-
"agentf-memory-neighbors": tool({
|
|
761
|
-
description: "Get neighboring memory nodes by edge traversal.",
|
|
762
|
-
args: {
|
|
763
|
-
node_id: tool.schema.string(),
|
|
764
|
-
relation: tool.schema.string().optional(),
|
|
765
|
-
depth: tool.schema.number().int().optional(),
|
|
766
|
-
limit: tool.schema.number().int().optional(),
|
|
767
|
-
},
|
|
768
|
-
async execute(args, context) {
|
|
769
|
-
const commandArgs = [args.node_id];
|
|
770
|
-
if (args.relation) commandArgs.push(`--relation=${args.relation}`);
|
|
771
|
-
if (Number.isInteger(args.depth)) commandArgs.push(`--depth=${String(args.depth)}`);
|
|
772
|
-
if (Number.isInteger(args.limit)) commandArgs.push(`-n`, String(args.limit));
|
|
773
|
-
return runAgentfCli(context.directory, "memory", "neighbors", commandArgs);
|
|
774
|
-
},
|
|
775
|
-
}),
|
|
776
|
-
"agentf-memory-subgraph": tool({
|
|
777
|
-
description: "Build a subgraph from seed ids.",
|
|
778
|
-
args: {
|
|
779
|
-
seed_ids: tool.schema.array(tool.schema.string()),
|
|
780
|
-
relation_filters: tool.schema.array(tool.schema.string()).optional(),
|
|
781
|
-
depth: tool.schema.number().int().optional(),
|
|
782
|
-
limit: tool.schema.number().int().optional(),
|
|
783
|
-
},
|
|
784
|
-
async execute(args, context) {
|
|
785
|
-
const seeds = (args.seed_ids || []).join(",");
|
|
786
|
-
const commandArgs = [seeds];
|
|
787
|
-
if (args.relation_filters?.length) commandArgs.push(`--relation=${args.relation_filters.join(",")}`);
|
|
788
|
-
if (Number.isInteger(args.depth)) commandArgs.push(`--depth=${String(args.depth)}`);
|
|
789
|
-
if (Number.isInteger(args.limit)) commandArgs.push(`-n`, String(args.limit));
|
|
790
|
-
return runAgentfCli(context.directory, "memory", "subgraph", commandArgs);
|
|
791
|
-
},
|
|
792
|
-
}),
|
|
793
|
-
"agentf-memory-add-lesson": tool({
|
|
794
|
-
description: "Store a lesson memory in Redis.",
|
|
795
|
-
args: {
|
|
796
|
-
title: tool.schema.string(),
|
|
797
|
-
description: tool.schema.string(),
|
|
798
|
-
agent: tool.schema.string().optional(),
|
|
799
|
-
tags: tool.schema.array(tool.schema.string()).optional(),
|
|
800
|
-
context: tool.schema.string().optional(),
|
|
801
|
-
},
|
|
802
|
-
async execute(args, context) {
|
|
803
|
-
const commandArgs = [args.title, args.description];
|
|
804
|
-
if (args.agent) commandArgs.push(`--agent=${args.agent}`);
|
|
805
|
-
if (args.tags?.length) commandArgs.push(`--tags=${args.tags.join(",")}`);
|
|
806
|
-
if (args.context) commandArgs.push(`--context=${args.context}`);
|
|
912
|
+
// Guard: agents directory may not exist in minimal workspaces (eg. tests).
|
|
913
|
+
if (fs.existsSync(absDir)) {
|
|
914
|
+
for (const file of fs.readdirSync(absDir)) {
|
|
915
|
+
const full = path.join(absDir, file);
|
|
916
|
+
if (!fs.statSync(full).isFile()) continue;
|
|
917
|
+
const content = fs.readFileSync(full, "utf8");
|
|
918
|
+
const fm = parseFrontmatter(content);
|
|
919
|
+
const toolName = fm["name"] || path.basename(file, path.extname(file));
|
|
807
920
|
|
|
808
|
-
|
|
809
|
-
},
|
|
810
|
-
}),
|
|
811
|
-
"agentf-memory-add-success": tool({
|
|
812
|
-
description: "Store a success memory in Redis.",
|
|
813
|
-
args: {
|
|
814
|
-
title: tool.schema.string(),
|
|
815
|
-
description: tool.schema.string(),
|
|
816
|
-
agent: tool.schema.string().optional(),
|
|
817
|
-
tags: tool.schema.array(tool.schema.string()).optional(),
|
|
818
|
-
context: tool.schema.string().optional(),
|
|
819
|
-
},
|
|
820
|
-
async execute(args, context) {
|
|
821
|
-
const commandArgs = [args.title, args.description];
|
|
822
|
-
if (args.agent) commandArgs.push(`--agent=${args.agent}`);
|
|
823
|
-
if (args.tags?.length) commandArgs.push(`--tags=${args.tags.join(",")}`);
|
|
824
|
-
if (args.context) commandArgs.push(`--context=${args.context}`);
|
|
921
|
+
if ((staticTools as any)[toolName]) continue;
|
|
825
922
|
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
description: "Store a pitfall memory in Redis.",
|
|
923
|
+
const agentName = toolName.replace(/^agentf-/, "");
|
|
924
|
+
|
|
925
|
+
agentTools[toolName] = tool({
|
|
926
|
+
description: `Invoke agent ${agentName} via the agentf CLI. If the result includes confirmation_required=true, ask the user before retrying with confirmedWrite=confirmed.`,
|
|
831
927
|
args: {
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
agent: tool.schema.string().optional(),
|
|
835
|
-
tags: tool.schema.array(tool.schema.string()).optional(),
|
|
836
|
-
context: tool.schema.string().optional(),
|
|
928
|
+
input: tool.schema.string().optional().describe("Optional input prompt or payload"),
|
|
929
|
+
confirmedWrite: tool.schema.string().optional().describe("Continuation token for confirmed writes"),
|
|
837
930
|
},
|
|
838
|
-
async execute(
|
|
839
|
-
const
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
931
|
+
async execute(_args: any, context: any) {
|
|
932
|
+
const cmdArgs: string[] = [];
|
|
933
|
+
// Ensure complex payloads are passed as a single JSON argument so the
|
|
934
|
+
// Ruby CLI can parse structured tasks. Accept strings as-is but
|
|
935
|
+
// stringify objects to avoid `[object Object]` being sent.
|
|
936
|
+
if (_args.input !== undefined) {
|
|
937
|
+
if (typeof _args.input === "object") {
|
|
938
|
+
cmdArgs.push(JSON.stringify(_args.input));
|
|
939
|
+
} else {
|
|
940
|
+
cmdArgs.push(String(_args.input));
|
|
941
|
+
}
|
|
942
|
+
}
|
|
943
|
+
if (_args.confirmedWrite) cmdArgs.push(`--confirmed-write=${_args.confirmedWrite}`);
|
|
944
|
+
return runAgentfCli(context.directory, "agent", agentName, cmdArgs);
|
|
845
945
|
},
|
|
846
|
-
})
|
|
847
|
-
}
|
|
848
|
-
}
|
|
946
|
+
});
|
|
947
|
+
}
|
|
948
|
+
}
|
|
949
|
+
|
|
950
|
+
const tools = { ...staticTools, ...agentTools };
|
|
951
|
+
|
|
952
|
+
// The plugin host expects a `tool` map (singular key) in the returned hooks.
|
|
953
|
+
return { tool: tools };
|
|
849
954
|
};
|
|
850
955
|
|
|
851
956
|
export default agentfPlugin;
|
|
852
957
|
TYPESCRIPT
|
|
853
958
|
end
|
|
854
959
|
|
|
855
|
-
def render_opencode_json
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
960
|
+
def render_opencode_json(root)
|
|
961
|
+
JSON.pretty_generate(opencode_json_config(root))
|
|
962
|
+
end
|
|
963
|
+
|
|
964
|
+
def opencode_json_config(root)
|
|
965
|
+
base = {
|
|
966
|
+
"$schema" => "https://opencode.ai/config.json"
|
|
967
|
+
}
|
|
968
|
+
|
|
969
|
+
if opencode_plugin_runtime?
|
|
970
|
+
base["plugin"] = ["./.opencode/plugins/agentf-plugin"]
|
|
971
|
+
else
|
|
972
|
+
base["mcp"] = {
|
|
973
|
+
"agentf" => {
|
|
974
|
+
"type" => "local",
|
|
975
|
+
"enabled" => true,
|
|
976
|
+
"command" => opencode_mcp_command(root)
|
|
977
|
+
}
|
|
860
978
|
}
|
|
861
|
-
|
|
979
|
+
end
|
|
980
|
+
|
|
981
|
+
base
|
|
982
|
+
end
|
|
983
|
+
|
|
984
|
+
def opencode_mcp_command(root)
|
|
985
|
+
[File.join(root, "bin", "agentf"), "mcp-server"]
|
|
862
986
|
end
|
|
863
987
|
|
|
864
988
|
def write_opencode_json(root)
|
|
865
989
|
path = File.join(root, "opencode.json")
|
|
866
|
-
new_content = JSON.parse(render_opencode_json)
|
|
990
|
+
new_content = JSON.parse(render_opencode_json(root))
|
|
867
991
|
|
|
868
992
|
return write_manifest(path, JSON.pretty_generate(new_content)) unless File.exist?(path)
|
|
869
993
|
|
|
@@ -875,8 +999,22 @@ module Agentf
|
|
|
875
999
|
end
|
|
876
1000
|
|
|
877
1001
|
merged = existing.dup
|
|
878
|
-
|
|
879
|
-
|
|
1002
|
+
|
|
1003
|
+
if new_content["plugin"]
|
|
1004
|
+
merged_plugins = Array(existing["plugin"]) + Array(new_content["plugin"])
|
|
1005
|
+
merged["plugin"] = merged_plugins.uniq
|
|
1006
|
+
elsif existing["plugin"]
|
|
1007
|
+
filtered_plugins = Array(existing["plugin"]).reject { |entry| entry == "./.opencode/plugins/agentf-plugin" }
|
|
1008
|
+
if filtered_plugins.empty?
|
|
1009
|
+
merged.delete("plugin")
|
|
1010
|
+
else
|
|
1011
|
+
merged["plugin"] = filtered_plugins
|
|
1012
|
+
end
|
|
1013
|
+
end
|
|
1014
|
+
|
|
1015
|
+
if new_content["mcp"]
|
|
1016
|
+
merged["mcp"] = (existing["mcp"] || {}).merge(new_content["mcp"])
|
|
1017
|
+
end
|
|
880
1018
|
|
|
881
1019
|
write_manifest(path, JSON.pretty_generate(merged))
|
|
882
1020
|
end
|