opencode-froggy 0.1.0 → 0.2.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.
- package/LICENSE +21 -0
- package/README.md +383 -246
- package/agent/architect.md +91 -0
- package/agent/partner.md +143 -0
- package/agent/rubber-duck.md +129 -0
- package/command/commit-push.md +21 -0
- package/command/doc-changes.md +45 -0
- package/command/review-changes.md +1 -21
- package/command/review-pr.md +1 -22
- package/command/send-to.md +21 -0
- package/command/simplify-changes.md +2 -20
- package/dist/index.d.ts +1 -1
- package/dist/index.js +16 -52
- package/dist/index.test.js +29 -8
- package/dist/loaders.d.ts +9 -5
- package/dist/loaders.js +5 -1
- package/dist/tools/diff-summary.d.ts +20 -0
- package/dist/tools/diff-summary.js +111 -0
- package/dist/tools/gitingest.d.ts +26 -0
- package/dist/tools/gitingest.js +41 -0
- package/dist/tools/index.d.ts +4 -0
- package/dist/tools/index.js +4 -0
- package/dist/tools/list-child-sessions.d.ts +9 -0
- package/dist/tools/list-child-sessions.js +24 -0
- package/dist/tools/prompt-session.d.ts +19 -0
- package/dist/tools/prompt-session.js +39 -0
- package/dist/tools/reply-child.d.ts +19 -0
- package/dist/tools/reply-child.js +42 -0
- package/images/logo.png +0 -0
- package/package.json +4 -2
- package/command/commit.md +0 -18
package/dist/index.test.js
CHANGED
|
@@ -113,16 +113,39 @@ Content`;
|
|
|
113
113
|
expect(result["minimal"]).not.toHaveProperty("tools");
|
|
114
114
|
expect(result["minimal"]).not.toHaveProperty("permissions");
|
|
115
115
|
});
|
|
116
|
-
it("should
|
|
117
|
-
const
|
|
116
|
+
it("should use mode value directly (primary, subagent, all)", () => {
|
|
117
|
+
const primaryContent = `---
|
|
118
118
|
description: Primary agent
|
|
119
|
-
mode:
|
|
119
|
+
mode: primary
|
|
120
120
|
---
|
|
121
121
|
|
|
122
122
|
Content`;
|
|
123
|
-
|
|
123
|
+
const subagentContent = `---
|
|
124
|
+
description: Subagent
|
|
125
|
+
mode: subagent
|
|
126
|
+
---
|
|
127
|
+
|
|
128
|
+
Content`;
|
|
129
|
+
const allContent = `---
|
|
130
|
+
description: All modes agent
|
|
131
|
+
mode: all
|
|
132
|
+
---
|
|
133
|
+
|
|
134
|
+
Content`;
|
|
135
|
+
const noModeContent = `---
|
|
136
|
+
description: No mode specified
|
|
137
|
+
---
|
|
138
|
+
|
|
139
|
+
Content`;
|
|
140
|
+
writeFileSync(join(testDir, "primary.md"), primaryContent);
|
|
141
|
+
writeFileSync(join(testDir, "subagent.md"), subagentContent);
|
|
142
|
+
writeFileSync(join(testDir, "all.md"), allContent);
|
|
143
|
+
writeFileSync(join(testDir, "nomode.md"), noModeContent);
|
|
124
144
|
const result = loadAgents(testDir);
|
|
125
145
|
expect(result["primary"].mode).toBe("primary");
|
|
146
|
+
expect(result["subagent"].mode).toBe("subagent");
|
|
147
|
+
expect(result["all"].mode).toBe("all");
|
|
148
|
+
expect(result["nomode"].mode).toBe("all");
|
|
126
149
|
});
|
|
127
150
|
it("should ignore non-markdown files", () => {
|
|
128
151
|
writeFileSync(join(testDir, "not-agent.txt"), "some content");
|
|
@@ -286,7 +309,6 @@ hooks:
|
|
|
286
309
|
conditions: [isMainSession]
|
|
287
310
|
actions:
|
|
288
311
|
- command: simplify-changes
|
|
289
|
-
- skill: post-change-code-simplification
|
|
290
312
|
- tool:
|
|
291
313
|
name: bash
|
|
292
314
|
args:
|
|
@@ -296,10 +318,9 @@ hooks:
|
|
|
296
318
|
const result = loadHooks(testDir);
|
|
297
319
|
const hooks = result.get("session.idle");
|
|
298
320
|
expect(hooks).toHaveLength(1);
|
|
299
|
-
expect(hooks[0].actions).toHaveLength(
|
|
321
|
+
expect(hooks[0].actions).toHaveLength(2);
|
|
300
322
|
expect(hooks[0].actions[0]).toEqual({ command: "simplify-changes" });
|
|
301
|
-
expect(hooks[0].actions[1]).toEqual({
|
|
302
|
-
expect(hooks[0].actions[2]).toEqual({
|
|
323
|
+
expect(hooks[0].actions[1]).toEqual({
|
|
303
324
|
tool: { name: "bash", args: { command: "echo done" } }
|
|
304
325
|
});
|
|
305
326
|
});
|
package/dist/loaders.d.ts
CHANGED
|
@@ -1,7 +1,10 @@
|
|
|
1
1
|
export interface AgentFrontmatter {
|
|
2
2
|
description: string;
|
|
3
|
-
mode?: "subagent" | "
|
|
3
|
+
mode?: "primary" | "subagent" | "all";
|
|
4
|
+
model?: string;
|
|
4
5
|
temperature?: number;
|
|
6
|
+
maxSteps?: number;
|
|
7
|
+
disable?: boolean;
|
|
5
8
|
tools?: Record<string, boolean>;
|
|
6
9
|
permission?: Record<string, unknown>;
|
|
7
10
|
permissions?: Record<string, unknown>;
|
|
@@ -17,6 +20,7 @@ export interface CommandFrontmatter {
|
|
|
17
20
|
description: string;
|
|
18
21
|
agent?: string;
|
|
19
22
|
model?: string;
|
|
23
|
+
subtask?: boolean;
|
|
20
24
|
}
|
|
21
25
|
export interface CommandConfig {
|
|
22
26
|
template: string;
|
|
@@ -39,9 +43,6 @@ export interface HookActionCommand {
|
|
|
39
43
|
args: string;
|
|
40
44
|
};
|
|
41
45
|
}
|
|
42
|
-
export interface HookActionSkill {
|
|
43
|
-
skill: string;
|
|
44
|
-
}
|
|
45
46
|
export interface HookActionTool {
|
|
46
47
|
tool: {
|
|
47
48
|
name: string;
|
|
@@ -54,7 +55,7 @@ export interface HookActionBash {
|
|
|
54
55
|
timeout?: number;
|
|
55
56
|
};
|
|
56
57
|
}
|
|
57
|
-
export type HookAction = HookActionCommand |
|
|
58
|
+
export type HookAction = HookActionCommand | HookActionTool | HookActionBash;
|
|
58
59
|
export interface HookConfig {
|
|
59
60
|
event: HookEvent;
|
|
60
61
|
conditions?: HookCondition[];
|
|
@@ -63,7 +64,10 @@ export interface HookConfig {
|
|
|
63
64
|
export interface AgentConfigOutput {
|
|
64
65
|
description: string;
|
|
65
66
|
mode: "subagent" | "primary" | "all";
|
|
67
|
+
model?: string;
|
|
66
68
|
temperature?: number;
|
|
69
|
+
maxSteps?: number;
|
|
70
|
+
disable?: boolean;
|
|
67
71
|
tools?: Record<string, boolean>;
|
|
68
72
|
permissions?: Record<string, unknown>;
|
|
69
73
|
prompt: string;
|
package/dist/loaders.js
CHANGED
|
@@ -37,13 +37,16 @@ export function loadAgents(agentDir) {
|
|
|
37
37
|
const content = readFileSync(filePath, "utf-8");
|
|
38
38
|
const { data, body } = parseFrontmatter(content);
|
|
39
39
|
const agentName = basename(file, ".md");
|
|
40
|
-
const mode = data.mode
|
|
40
|
+
const mode = data.mode ?? "all";
|
|
41
41
|
const permissions = data.permissions ?? data.permission;
|
|
42
42
|
agents[agentName] = {
|
|
43
43
|
description: data.description || "",
|
|
44
44
|
mode,
|
|
45
45
|
prompt: body.trim(),
|
|
46
|
+
...(data.model !== undefined && { model: data.model }),
|
|
46
47
|
...(data.temperature !== undefined && { temperature: data.temperature }),
|
|
48
|
+
...(data.maxSteps !== undefined && { maxSteps: data.maxSteps }),
|
|
49
|
+
...(data.disable !== undefined && { disable: data.disable }),
|
|
47
50
|
...(data.tools !== undefined && { tools: data.tools }),
|
|
48
51
|
...(permissions !== undefined && { permissions }),
|
|
49
52
|
};
|
|
@@ -86,6 +89,7 @@ export function loadCommands(commandDir) {
|
|
|
86
89
|
description: data.description || "",
|
|
87
90
|
agent: data.agent,
|
|
88
91
|
model: data.model,
|
|
92
|
+
subtask: data.subtask,
|
|
89
93
|
template: body.trim(),
|
|
90
94
|
};
|
|
91
95
|
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { type ToolContext } from "@opencode-ai/plugin";
|
|
2
|
+
export interface DiffSummaryArgs {
|
|
3
|
+
source?: string;
|
|
4
|
+
target?: string;
|
|
5
|
+
remote?: string;
|
|
6
|
+
}
|
|
7
|
+
export declare function diffSummary(args: DiffSummaryArgs, cwd: string): Promise<string>;
|
|
8
|
+
export declare function createDiffSummaryTool(directory: string): {
|
|
9
|
+
description: string;
|
|
10
|
+
args: {
|
|
11
|
+
source: import("zod").ZodOptional<import("zod").ZodString>;
|
|
12
|
+
target: import("zod").ZodOptional<import("zod").ZodString>;
|
|
13
|
+
remote: import("zod").ZodOptional<import("zod").ZodString>;
|
|
14
|
+
};
|
|
15
|
+
execute(args: {
|
|
16
|
+
source?: string | undefined;
|
|
17
|
+
target?: string | undefined;
|
|
18
|
+
remote?: string | undefined;
|
|
19
|
+
}, context: ToolContext): Promise<string>;
|
|
20
|
+
};
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
import { execFile } from "node:child_process";
|
|
2
|
+
import { promisify } from "node:util";
|
|
3
|
+
import { tool } from "@opencode-ai/plugin";
|
|
4
|
+
import { log } from "../logger";
|
|
5
|
+
const execFileAsync = promisify(execFile);
|
|
6
|
+
const DIFF_CONTEXT_LINES = 5;
|
|
7
|
+
async function git(args, cwd) {
|
|
8
|
+
try {
|
|
9
|
+
const result = await execFileAsync("git", args, { cwd, maxBuffer: 10 * 1024 * 1024 });
|
|
10
|
+
return result.stdout;
|
|
11
|
+
}
|
|
12
|
+
catch (error) {
|
|
13
|
+
const execError = error;
|
|
14
|
+
if (execError.stdout)
|
|
15
|
+
return execError.stdout;
|
|
16
|
+
throw error;
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
async function getDiffSet(cwd, extraArgs = []) {
|
|
20
|
+
const [stats, files, diff] = await Promise.all([
|
|
21
|
+
git(["diff", ...extraArgs, "--stat"], cwd),
|
|
22
|
+
git(["diff", ...extraArgs, "--name-status"], cwd),
|
|
23
|
+
git(["diff", ...extraArgs, `-U${DIFF_CONTEXT_LINES}`, "--function-context"], cwd),
|
|
24
|
+
]);
|
|
25
|
+
return { stats, files, diff };
|
|
26
|
+
}
|
|
27
|
+
async function getBranchesDiff(source, target, remote, cwd) {
|
|
28
|
+
const refSource = remote ? `${remote}/${source}` : source;
|
|
29
|
+
const refTarget = remote ? `${remote}/${target}` : target;
|
|
30
|
+
const range = `${refTarget}...${refSource}`;
|
|
31
|
+
const rangeLog = `${refTarget}..${refSource}`;
|
|
32
|
+
if (remote) {
|
|
33
|
+
await git(["fetch", remote, source, target, "--prune"], cwd);
|
|
34
|
+
}
|
|
35
|
+
const [stats, commits, files, diff] = await Promise.all([
|
|
36
|
+
git(["diff", "--stat", range], cwd),
|
|
37
|
+
git(["log", "--oneline", "--no-merges", rangeLog], cwd),
|
|
38
|
+
git(["diff", "--name-only", range], cwd),
|
|
39
|
+
git(["diff", `-U${DIFF_CONTEXT_LINES}`, "--function-context", range], cwd),
|
|
40
|
+
]);
|
|
41
|
+
return [
|
|
42
|
+
"## Stats Overview", "```", stats.trim(), "```",
|
|
43
|
+
"",
|
|
44
|
+
"## Commits to Review", "```", commits.trim(), "```",
|
|
45
|
+
"",
|
|
46
|
+
"## Files Changed", "```", files.trim(), "```",
|
|
47
|
+
"",
|
|
48
|
+
"## Full Diff", "```diff", diff.trim(), "```",
|
|
49
|
+
].join("\n\n");
|
|
50
|
+
}
|
|
51
|
+
async function getWorkingTreeDiff(cwd) {
|
|
52
|
+
const sections = [];
|
|
53
|
+
const [status, staged, unstaged, untrackedList] = await Promise.all([
|
|
54
|
+
git(["status", "--porcelain=v1", "-uall"], cwd),
|
|
55
|
+
getDiffSet(cwd, ["--cached"]),
|
|
56
|
+
getDiffSet(cwd),
|
|
57
|
+
git(["ls-files", "--others", "--exclude-standard"], cwd),
|
|
58
|
+
]);
|
|
59
|
+
sections.push("## Status Overview", "```", status.trim() || "(no changes)", "```");
|
|
60
|
+
if (staged.stats.trim() || staged.files.trim()) {
|
|
61
|
+
sections.push("## Staged Changes", "### Stats", "```", staged.stats.trim() || "(none)", "```", "### Files", "```", staged.files.trim() || "(none)", "```", "### Diff", "```diff", staged.diff.trim() || "(none)", "```");
|
|
62
|
+
}
|
|
63
|
+
if (unstaged.stats.trim() || unstaged.files.trim()) {
|
|
64
|
+
sections.push("## Unstaged Changes", "### Stats", "```", unstaged.stats.trim() || "(none)", "```", "### Files", "```", unstaged.files.trim() || "(none)", "```", "### Diff", "```diff", unstaged.diff.trim() || "(none)", "```");
|
|
65
|
+
}
|
|
66
|
+
const untrackedFiles = untrackedList.trim().split("\n").filter(Boolean);
|
|
67
|
+
if (untrackedFiles.length > 0) {
|
|
68
|
+
const untrackedDiffs = [];
|
|
69
|
+
for (const file of untrackedFiles) {
|
|
70
|
+
try {
|
|
71
|
+
const fileDiff = await git(["diff", "--no-index", `-U${DIFF_CONTEXT_LINES}`, "--function-context", "--", "/dev/null", file], cwd);
|
|
72
|
+
untrackedDiffs.push(`=== NEW: ${file} ===\n${fileDiff.trim()}`);
|
|
73
|
+
}
|
|
74
|
+
catch (error) {
|
|
75
|
+
log("[diff-summary] failed to diff untracked file", { file, error: String(error) });
|
|
76
|
+
untrackedDiffs.push(`=== NEW: ${file} === (could not diff)`);
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
sections.push("## Untracked Files", "```", untrackedFiles.join("\n"), "```", "### Diffs", "```diff", untrackedDiffs.join("\n\n"), "```");
|
|
80
|
+
}
|
|
81
|
+
return sections.join("\n\n");
|
|
82
|
+
}
|
|
83
|
+
export async function diffSummary(args, cwd) {
|
|
84
|
+
const { source, target = "main", remote = "origin" } = args;
|
|
85
|
+
if (source) {
|
|
86
|
+
return getBranchesDiff(source, target, remote, cwd);
|
|
87
|
+
}
|
|
88
|
+
return getWorkingTreeDiff(cwd);
|
|
89
|
+
}
|
|
90
|
+
export function createDiffSummaryTool(directory) {
|
|
91
|
+
return tool({
|
|
92
|
+
description: "Generate a structured summary of git diffs. Use for reviewing branches comparison or working tree changes. Returns stats, commits, files changed, and full diff.",
|
|
93
|
+
args: {
|
|
94
|
+
source: tool.schema
|
|
95
|
+
.string()
|
|
96
|
+
.optional()
|
|
97
|
+
.describe("Source branch to compare (e.g., 'feature-branch'). If omitted, analyzes working tree changes."),
|
|
98
|
+
target: tool.schema
|
|
99
|
+
.string()
|
|
100
|
+
.optional()
|
|
101
|
+
.describe("Target branch to compare against (default: 'main')"),
|
|
102
|
+
remote: tool.schema
|
|
103
|
+
.string()
|
|
104
|
+
.optional()
|
|
105
|
+
.describe("Git remote name (default: 'origin')"),
|
|
106
|
+
},
|
|
107
|
+
async execute(args, _context) {
|
|
108
|
+
return diffSummary(args, directory);
|
|
109
|
+
},
|
|
110
|
+
});
|
|
111
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { type ToolContext } from "@opencode-ai/plugin";
|
|
2
|
+
export interface GitingestArgs {
|
|
3
|
+
url: string;
|
|
4
|
+
maxFileSize?: number;
|
|
5
|
+
pattern?: string;
|
|
6
|
+
patternType?: "include" | "exclude";
|
|
7
|
+
}
|
|
8
|
+
export declare function fetchGitingest(args: GitingestArgs): Promise<string>;
|
|
9
|
+
export declare const gitingestTool: {
|
|
10
|
+
description: string;
|
|
11
|
+
args: {
|
|
12
|
+
url: import("zod").ZodString;
|
|
13
|
+
maxFileSize: import("zod").ZodOptional<import("zod").ZodNumber>;
|
|
14
|
+
pattern: import("zod").ZodOptional<import("zod").ZodString>;
|
|
15
|
+
patternType: import("zod").ZodOptional<import("zod").ZodEnum<{
|
|
16
|
+
include: "include";
|
|
17
|
+
exclude: "exclude";
|
|
18
|
+
}>>;
|
|
19
|
+
};
|
|
20
|
+
execute(args: {
|
|
21
|
+
url: string;
|
|
22
|
+
maxFileSize?: number | undefined;
|
|
23
|
+
pattern?: string | undefined;
|
|
24
|
+
patternType?: "include" | "exclude" | undefined;
|
|
25
|
+
}, context: ToolContext): Promise<string>;
|
|
26
|
+
};
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { tool } from "@opencode-ai/plugin";
|
|
2
|
+
export async function fetchGitingest(args) {
|
|
3
|
+
const response = await fetch("https://gitingest.com/api/ingest", {
|
|
4
|
+
method: "POST",
|
|
5
|
+
headers: { "Content-Type": "application/json" },
|
|
6
|
+
body: JSON.stringify({
|
|
7
|
+
input_text: args.url,
|
|
8
|
+
max_file_size: args.maxFileSize ?? 50000,
|
|
9
|
+
pattern: args.pattern ?? "",
|
|
10
|
+
pattern_type: args.patternType ?? "exclude",
|
|
11
|
+
}),
|
|
12
|
+
});
|
|
13
|
+
if (!response.ok) {
|
|
14
|
+
throw new Error(`gitingest API error: ${response.status} ${response.statusText}`);
|
|
15
|
+
}
|
|
16
|
+
const data = (await response.json());
|
|
17
|
+
return `${data.summary}\n\n${data.tree}\n\n${data.content}`;
|
|
18
|
+
}
|
|
19
|
+
export const gitingestTool = tool({
|
|
20
|
+
description: "Fetch a GitHub repository's full content via gitingest.com. Returns summary, directory tree, and file contents optimized for LLM analysis. Use when you need to understand an external repository's structure or code.",
|
|
21
|
+
args: {
|
|
22
|
+
url: tool.schema
|
|
23
|
+
.string()
|
|
24
|
+
.describe("GitHub repository URL (e.g., https://github.com/owner/repo)"),
|
|
25
|
+
maxFileSize: tool.schema
|
|
26
|
+
.number()
|
|
27
|
+
.optional()
|
|
28
|
+
.describe("Maximum file size in bytes to include (default: 50000)"),
|
|
29
|
+
pattern: tool.schema
|
|
30
|
+
.string()
|
|
31
|
+
.optional()
|
|
32
|
+
.describe("Glob pattern to filter files (e.g., '*.py' or 'src/*')"),
|
|
33
|
+
patternType: tool.schema
|
|
34
|
+
.enum(["include", "exclude"])
|
|
35
|
+
.optional()
|
|
36
|
+
.describe("Whether pattern includes or excludes matching files (default: exclude)"),
|
|
37
|
+
},
|
|
38
|
+
async execute(args, _context) {
|
|
39
|
+
return fetchGitingest(args);
|
|
40
|
+
},
|
|
41
|
+
});
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
export { gitingestTool, fetchGitingest, type GitingestArgs } from "./gitingest";
|
|
2
|
+
export { createDiffSummaryTool, diffSummary, type DiffSummaryArgs } from "./diff-summary";
|
|
3
|
+
export { createPromptSessionTool, type PromptSessionArgs } from "./prompt-session";
|
|
4
|
+
export { createListChildSessionsTool } from "./list-child-sessions";
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { type ToolContext } from "@opencode-ai/plugin";
|
|
2
|
+
import type { createOpencodeClient } from "@opencode-ai/sdk";
|
|
3
|
+
type Client = ReturnType<typeof createOpencodeClient>;
|
|
4
|
+
export declare function createListChildSessionsTool(client: Client): {
|
|
5
|
+
description: string;
|
|
6
|
+
args: {};
|
|
7
|
+
execute(args: Record<string, never>, context: ToolContext): Promise<string>;
|
|
8
|
+
};
|
|
9
|
+
export {};
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { tool } from "@opencode-ai/plugin";
|
|
2
|
+
import { log } from "../logger";
|
|
3
|
+
export function createListChildSessionsTool(client) {
|
|
4
|
+
return tool({
|
|
5
|
+
description: "List all child sessions (subagents) of the current session",
|
|
6
|
+
args: {},
|
|
7
|
+
async execute(_args, context) {
|
|
8
|
+
const children = await client.session.children({
|
|
9
|
+
path: { id: context.sessionID },
|
|
10
|
+
});
|
|
11
|
+
const childList = children.data ?? [];
|
|
12
|
+
if (childList.length === 0) {
|
|
13
|
+
return "No child sessions found";
|
|
14
|
+
}
|
|
15
|
+
log("[list-child-sessions] Found child sessions", { count: childList.length });
|
|
16
|
+
const formatted = childList.map((child, index) => {
|
|
17
|
+
const created = new Date(child.time.created).toISOString();
|
|
18
|
+
const updated = new Date(child.time.updated).toISOString();
|
|
19
|
+
return `${index + 1}. [${child.id}] ${child.title}\n Created: ${created} | Updated: ${updated}`;
|
|
20
|
+
}).join("\n\n");
|
|
21
|
+
return `Child sessions (${childList.length}):\n\n${formatted}`;
|
|
22
|
+
},
|
|
23
|
+
});
|
|
24
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { type ToolContext } from "@opencode-ai/plugin";
|
|
2
|
+
import type { createOpencodeClient } from "@opencode-ai/sdk";
|
|
3
|
+
type Client = ReturnType<typeof createOpencodeClient>;
|
|
4
|
+
export interface PromptSessionArgs {
|
|
5
|
+
message: string;
|
|
6
|
+
sessionId?: string;
|
|
7
|
+
}
|
|
8
|
+
export declare function createPromptSessionTool(client: Client): {
|
|
9
|
+
description: string;
|
|
10
|
+
args: {
|
|
11
|
+
message: import("zod").ZodString;
|
|
12
|
+
sessionId: import("zod").ZodOptional<import("zod").ZodString>;
|
|
13
|
+
};
|
|
14
|
+
execute(args: {
|
|
15
|
+
message: string;
|
|
16
|
+
sessionId?: string | undefined;
|
|
17
|
+
}, context: ToolContext): Promise<string>;
|
|
18
|
+
};
|
|
19
|
+
export {};
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { tool } from "@opencode-ai/plugin";
|
|
2
|
+
import { log } from "../logger";
|
|
3
|
+
export function createPromptSessionTool(client) {
|
|
4
|
+
return tool({
|
|
5
|
+
description: "Send a message to a child session (subagent) to continue the conversation",
|
|
6
|
+
args: {
|
|
7
|
+
message: tool.schema.string().describe("The message to send to the child session"),
|
|
8
|
+
sessionId: tool.schema.string().optional().describe("The child session ID to target (optional - uses last child if not provided)"),
|
|
9
|
+
},
|
|
10
|
+
async execute(args, context) {
|
|
11
|
+
let targetSessionId = args.sessionId;
|
|
12
|
+
if (!targetSessionId) {
|
|
13
|
+
const children = await client.session.children({
|
|
14
|
+
path: { id: context.sessionID },
|
|
15
|
+
});
|
|
16
|
+
const lastChild = (children.data ?? []).at(-1);
|
|
17
|
+
if (!lastChild) {
|
|
18
|
+
return "Error: No child session found for current session";
|
|
19
|
+
}
|
|
20
|
+
targetSessionId = lastChild.id;
|
|
21
|
+
}
|
|
22
|
+
log("[prompt-session] Sending message to child session", {
|
|
23
|
+
parentSessionID: context.sessionID,
|
|
24
|
+
childSessionID: targetSessionId,
|
|
25
|
+
messagePreview: args.message.slice(0, 100),
|
|
26
|
+
});
|
|
27
|
+
const response = await client.session.prompt({
|
|
28
|
+
path: { id: targetSessionId },
|
|
29
|
+
body: { parts: [{ type: "text", text: args.message }] },
|
|
30
|
+
});
|
|
31
|
+
const parts = response.data?.parts ?? [];
|
|
32
|
+
const textContent = parts
|
|
33
|
+
.filter((p) => p.type === "text" && p.text)
|
|
34
|
+
.map((p) => p.text)
|
|
35
|
+
.join("\n");
|
|
36
|
+
return textContent || "Message sent to child session";
|
|
37
|
+
},
|
|
38
|
+
});
|
|
39
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { type ToolContext } from "@opencode-ai/plugin";
|
|
2
|
+
import type { createOpencodeClient } from "@opencode-ai/sdk";
|
|
3
|
+
type Client = ReturnType<typeof createOpencodeClient>;
|
|
4
|
+
export interface ReplyChildArgs {
|
|
5
|
+
message: string;
|
|
6
|
+
sessionId?: string;
|
|
7
|
+
}
|
|
8
|
+
export declare function createReplyChildTool(client: Client): {
|
|
9
|
+
description: string;
|
|
10
|
+
args: {
|
|
11
|
+
message: import("zod").ZodString;
|
|
12
|
+
sessionId: import("zod").ZodOptional<import("zod").ZodString>;
|
|
13
|
+
};
|
|
14
|
+
execute(args: {
|
|
15
|
+
message: string;
|
|
16
|
+
sessionId?: string | undefined;
|
|
17
|
+
}, context: ToolContext): Promise<string>;
|
|
18
|
+
};
|
|
19
|
+
export {};
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { tool } from "@opencode-ai/plugin";
|
|
2
|
+
import { log } from "../logger";
|
|
3
|
+
export function createReplyChildTool(client) {
|
|
4
|
+
return tool({
|
|
5
|
+
description: "Send a message to the last child session (subagent) to continue the conversation",
|
|
6
|
+
args: {
|
|
7
|
+
message: tool.schema.string().describe("The message to send to the child session"),
|
|
8
|
+
sessionId: tool.schema.string().optional().describe("The child session ID to target (optional - uses last child if not provided)"),
|
|
9
|
+
},
|
|
10
|
+
async execute(args, context) {
|
|
11
|
+
let targetSessionId = args.sessionId;
|
|
12
|
+
if (!targetSessionId) {
|
|
13
|
+
const children = await client.session.children({
|
|
14
|
+
path: { id: context.sessionID },
|
|
15
|
+
});
|
|
16
|
+
const lastChild = (children.data ?? []).at(-1);
|
|
17
|
+
if (!lastChild) {
|
|
18
|
+
return "Error: No child session found for current session";
|
|
19
|
+
}
|
|
20
|
+
targetSessionId = lastChild.id;
|
|
21
|
+
}
|
|
22
|
+
log("[reply-child] Sending message to child session", {
|
|
23
|
+
parentSessionID: context.sessionID,
|
|
24
|
+
childSessionID: targetSessionId,
|
|
25
|
+
message: args.message.slice(0, 100),
|
|
26
|
+
});
|
|
27
|
+
const response = await client.session.prompt({
|
|
28
|
+
path: { id: targetSessionId },
|
|
29
|
+
body: { parts: [{ type: "text", text: args.message }] },
|
|
30
|
+
});
|
|
31
|
+
log("[reply-child] Response received", {
|
|
32
|
+
childSessionID: targetSessionId,
|
|
33
|
+
});
|
|
34
|
+
const parts = response.data?.parts ?? [];
|
|
35
|
+
const textContent = parts
|
|
36
|
+
.filter((p) => p.type === "text" && p.text)
|
|
37
|
+
.map((p) => p.text)
|
|
38
|
+
.join("\n");
|
|
39
|
+
return textContent || "Message sent to child session";
|
|
40
|
+
},
|
|
41
|
+
});
|
|
42
|
+
}
|
package/images/logo.png
ADDED
|
Binary file
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "opencode-froggy",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"description": "OpenCode plugin with a hook layer (tool.before.*, session.idle...), agents (code-reviewer, doc-writer), and commands (/review-pr, /commit)",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|
|
@@ -9,7 +9,9 @@
|
|
|
9
9
|
"dist",
|
|
10
10
|
"agent",
|
|
11
11
|
"command",
|
|
12
|
-
"skill"
|
|
12
|
+
"skill",
|
|
13
|
+
"images",
|
|
14
|
+
"README.md"
|
|
13
15
|
],
|
|
14
16
|
"scripts": {
|
|
15
17
|
"build": "tsc",
|
package/command/commit.md
DELETED
|
@@ -1,18 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
description: Commit and push
|
|
3
|
-
agent: build
|
|
4
|
-
---
|
|
5
|
-
|
|
6
|
-
## Context
|
|
7
|
-
|
|
8
|
-
- Current git status: !`git status`
|
|
9
|
-
- Current git diff (staged and unstaged changes): !`git diff HEAD`
|
|
10
|
-
- Current branch: !`git branch --show-current`
|
|
11
|
-
|
|
12
|
-
## Your task
|
|
13
|
-
|
|
14
|
-
Based on the above changes:
|
|
15
|
-
1. Create a new branch if on main or master
|
|
16
|
-
2. Create a single commit with an appropriate message
|
|
17
|
-
3. Push the branch to origin
|
|
18
|
-
4. You have the capability to call multiple tools in a single response. You MUST do all of the above in a single message. Do not use any other tools or do anything else. Do not send any other text or messages besides these tool calls.
|