@jmylchreest/aide-plugin 0.0.24 → 0.0.26
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/package.json +2 -1
- package/skills/build-fix/SKILL.md +162 -0
- package/skills/code-search/SKILL.md +108 -0
- package/skills/debug/SKILL.md +189 -0
- package/skills/decide/SKILL.md +225 -0
- package/skills/design/SKILL.md +192 -0
- package/skills/docs/SKILL.md +269 -0
- package/skills/git/SKILL.md +181 -0
- package/skills/implement/SKILL.md +240 -0
- package/skills/memorise/SKILL.md +139 -0
- package/skills/perf/SKILL.md +305 -0
- package/skills/plan-swarm/SKILL.md +135 -0
- package/skills/ralph/SKILL.md +483 -0
- package/skills/recall/SKILL.md +90 -0
- package/skills/review/SKILL.md +147 -0
- package/skills/swarm/SKILL.md +501 -0
- package/skills/test/SKILL.md +244 -0
- package/skills/verify/SKILL.md +299 -0
- package/skills/worktree-resolve/SKILL.md +342 -0
- package/src/core/index.ts +1 -0
- package/src/core/partial-memory.ts +354 -0
- package/src/core/session-init.ts +0 -3
- package/src/core/session-summary-logic.ts +2 -6
- package/src/opencode/hooks.ts +237 -25
- package/src/opencode/index.ts +10 -1
- package/src/opencode/types.ts +98 -3
|
@@ -169,9 +169,7 @@ export function buildSessionSummaryFromState(cwd: string): string | null {
|
|
|
169
169
|
const summaryParts: string[] = [];
|
|
170
170
|
|
|
171
171
|
if (commits.length > 0) {
|
|
172
|
-
summaryParts.push(
|
|
173
|
-
`## Commits\n${commits.map((c) => `- ${c}`).join("\n")}`,
|
|
174
|
-
);
|
|
172
|
+
summaryParts.push(`## Commits\n${commits.map((c) => `- ${c}`).join("\n")}`);
|
|
175
173
|
}
|
|
176
174
|
|
|
177
175
|
// Check for modified files via git
|
|
@@ -216,14 +214,12 @@ export function storeSessionSummary(
|
|
|
216
214
|
summary: string,
|
|
217
215
|
): boolean {
|
|
218
216
|
try {
|
|
219
|
-
const dbPath = join(cwd, ".aide", "memory", "store.db");
|
|
220
|
-
const env = { ...process.env, AIDE_MEMORY_DB: dbPath };
|
|
221
217
|
const tags = `session-summary,session:${sessionId.slice(0, 8)}`;
|
|
222
218
|
|
|
223
219
|
execFileSync(
|
|
224
220
|
binary,
|
|
225
221
|
["memory", "add", "--category=session", `--tags=${tags}`, summary],
|
|
226
|
-
{
|
|
222
|
+
{ cwd, stdio: "pipe", timeout: 5000 },
|
|
227
223
|
);
|
|
228
224
|
|
|
229
225
|
return true;
|
package/src/opencode/hooks.ts
CHANGED
|
@@ -25,6 +25,7 @@
|
|
|
25
25
|
* Stop (blocking) → session.idle re-prompts via session.prompt() for persistence
|
|
26
26
|
*/
|
|
27
27
|
|
|
28
|
+
import { execFileSync } from "child_process";
|
|
28
29
|
import { findAideBinary } from "../core/aide-client.js";
|
|
29
30
|
import {
|
|
30
31
|
ensureDirectories,
|
|
@@ -56,10 +57,24 @@ import { saveStateSnapshot } from "../core/pre-compact-logic.js";
|
|
|
56
57
|
import { cleanupSession } from "../core/cleanup.js";
|
|
57
58
|
import {
|
|
58
59
|
buildSessionSummaryFromState,
|
|
60
|
+
getSessionCommits,
|
|
59
61
|
storeSessionSummary,
|
|
60
62
|
} from "../core/session-summary-logic.js";
|
|
63
|
+
import {
|
|
64
|
+
storePartialMemory,
|
|
65
|
+
gatherPartials,
|
|
66
|
+
buildSummaryFromPartials,
|
|
67
|
+
cleanupPartials,
|
|
68
|
+
} from "../core/partial-memory.js";
|
|
61
69
|
import type { MemoryInjection, SessionState } from "../core/types.js";
|
|
62
|
-
import type {
|
|
70
|
+
import type {
|
|
71
|
+
Hooks,
|
|
72
|
+
OpenCodeClient,
|
|
73
|
+
OpenCodeConfig,
|
|
74
|
+
OpenCodeEvent,
|
|
75
|
+
OpenCodeSession,
|
|
76
|
+
OpenCodePart,
|
|
77
|
+
} from "./types.js";
|
|
63
78
|
import { debug } from "../lib/logger.js";
|
|
64
79
|
|
|
65
80
|
const SOURCE = "opencode-hooks";
|
|
@@ -81,6 +96,8 @@ interface AideState {
|
|
|
81
96
|
binary: string | null;
|
|
82
97
|
cwd: string;
|
|
83
98
|
worktree: string;
|
|
99
|
+
/** Root of the aide plugin package (for finding bundled skills) */
|
|
100
|
+
pluginRoot: string | null;
|
|
84
101
|
sessionState: SessionState | null;
|
|
85
102
|
memories: MemoryInjection | null;
|
|
86
103
|
welcomeContext: string | null;
|
|
@@ -90,6 +107,8 @@ interface AideState {
|
|
|
90
107
|
processedMessageParts: Set<string>;
|
|
91
108
|
/** Matched skills pending injection via system transform */
|
|
92
109
|
pendingSkillsContext: string | null;
|
|
110
|
+
/** Last user prompt text, used for skill matching in system transform */
|
|
111
|
+
lastUserPrompt: string | null;
|
|
93
112
|
/** Per-session metadata for agent-like tracking */
|
|
94
113
|
sessionInfoMap: Map<string, SessionInfo>;
|
|
95
114
|
client: OpenCodeClient;
|
|
@@ -102,18 +121,21 @@ export async function createHooks(
|
|
|
102
121
|
cwd: string,
|
|
103
122
|
worktree: string,
|
|
104
123
|
client: OpenCodeClient,
|
|
124
|
+
pluginRoot?: string,
|
|
105
125
|
): Promise<Hooks> {
|
|
106
126
|
const state: AideState = {
|
|
107
127
|
initialized: false,
|
|
108
128
|
binary: null,
|
|
109
129
|
cwd,
|
|
110
130
|
worktree,
|
|
131
|
+
pluginRoot: pluginRoot || null,
|
|
111
132
|
sessionState: null,
|
|
112
133
|
memories: null,
|
|
113
134
|
welcomeContext: null,
|
|
114
135
|
initializedSessions: new Set(),
|
|
115
136
|
processedMessageParts: new Set(),
|
|
116
137
|
pendingSkillsContext: null,
|
|
138
|
+
lastUserPrompt: null,
|
|
117
139
|
sessionInfoMap: new Map(),
|
|
118
140
|
client,
|
|
119
141
|
};
|
|
@@ -123,6 +145,8 @@ export async function createHooks(
|
|
|
123
145
|
|
|
124
146
|
return {
|
|
125
147
|
event: createEventHandler(state),
|
|
148
|
+
config: createConfigHandler(state),
|
|
149
|
+
"command.execute.before": createCommandHandler(state),
|
|
126
150
|
"tool.execute.before": createToolBeforeHandler(state),
|
|
127
151
|
"tool.execute.after": createToolAfterHandler(state),
|
|
128
152
|
"experimental.session.compacting": createCompactionHandler(state),
|
|
@@ -132,6 +156,94 @@ export async function createHooks(
|
|
|
132
156
|
};
|
|
133
157
|
}
|
|
134
158
|
|
|
159
|
+
// =============================================================================
|
|
160
|
+
// Config handler (register aide commands as OpenCode slash commands)
|
|
161
|
+
// =============================================================================
|
|
162
|
+
|
|
163
|
+
function createConfigHandler(
|
|
164
|
+
state: AideState,
|
|
165
|
+
): (input: OpenCodeConfig) => Promise<void> {
|
|
166
|
+
return async (input) => {
|
|
167
|
+
try {
|
|
168
|
+
// Discover all skills and register them as OpenCode commands
|
|
169
|
+
const skills = discoverSkills(state.cwd, state.pluginRoot ?? undefined);
|
|
170
|
+
|
|
171
|
+
if (!input.command) {
|
|
172
|
+
input.command = {};
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
for (const skill of skills) {
|
|
176
|
+
const commandName = `aide:${skill.name}`;
|
|
177
|
+
// Only register if not already defined (user config takes priority)
|
|
178
|
+
if (!input.command[commandName]) {
|
|
179
|
+
input.command[commandName] = {
|
|
180
|
+
template: `Activate the aide "${skill.name}" skill. {{arguments}}`,
|
|
181
|
+
description: skill.description || `aide ${skill.name} skill`,
|
|
182
|
+
};
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
debug(
|
|
187
|
+
SOURCE,
|
|
188
|
+
`Registered ${skills.length} aide commands via config hook`,
|
|
189
|
+
);
|
|
190
|
+
} catch (err) {
|
|
191
|
+
debug(SOURCE, `Config hook failed (non-fatal): ${err}`);
|
|
192
|
+
}
|
|
193
|
+
};
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
// =============================================================================
|
|
197
|
+
// Command handler (intercept aide slash command execution)
|
|
198
|
+
// =============================================================================
|
|
199
|
+
|
|
200
|
+
function createCommandHandler(state: AideState): (
|
|
201
|
+
input: { command: string; sessionID: string; arguments: string },
|
|
202
|
+
output: {
|
|
203
|
+
parts: Array<{ type: string; text: string; [key: string]: unknown }>;
|
|
204
|
+
},
|
|
205
|
+
) => Promise<void> {
|
|
206
|
+
return async (input, output) => {
|
|
207
|
+
// Only handle aide: prefixed commands
|
|
208
|
+
if (!input.command.startsWith("aide:")) return;
|
|
209
|
+
|
|
210
|
+
const skillName = input.command.slice("aide:".length);
|
|
211
|
+
const args = input.arguments || "";
|
|
212
|
+
|
|
213
|
+
try {
|
|
214
|
+
const skills = discoverSkills(state.cwd, state.pluginRoot ?? undefined);
|
|
215
|
+
const skill = skills.find((s) => s.name === skillName);
|
|
216
|
+
|
|
217
|
+
if (skill) {
|
|
218
|
+
// Format the skill content for injection
|
|
219
|
+
const context = formatSkillsContext([skill]);
|
|
220
|
+
|
|
221
|
+
// Store for system transform injection
|
|
222
|
+
state.pendingSkillsContext = context;
|
|
223
|
+
|
|
224
|
+
// Also store the arguments as the user prompt for the transform
|
|
225
|
+
if (args) {
|
|
226
|
+
state.lastUserPrompt = args;
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
debug(SOURCE, `Command handler activated skill: ${skillName}`);
|
|
230
|
+
|
|
231
|
+
await state.client.app.log({
|
|
232
|
+
body: {
|
|
233
|
+
service: "aide",
|
|
234
|
+
level: "info",
|
|
235
|
+
message: `Activated skill: ${skillName}${args ? ` with args: ${args.slice(0, 50)}` : ""}`,
|
|
236
|
+
},
|
|
237
|
+
});
|
|
238
|
+
} else {
|
|
239
|
+
debug(SOURCE, `Command handler: unknown skill "${skillName}"`);
|
|
240
|
+
}
|
|
241
|
+
} catch (err) {
|
|
242
|
+
debug(SOURCE, `Command handler failed (non-fatal): ${err}`);
|
|
243
|
+
}
|
|
244
|
+
};
|
|
245
|
+
}
|
|
246
|
+
|
|
135
247
|
// =============================================================================
|
|
136
248
|
// Initialization
|
|
137
249
|
// =============================================================================
|
|
@@ -195,11 +307,33 @@ function createEventHandler(
|
|
|
195
307
|
};
|
|
196
308
|
}
|
|
197
309
|
|
|
310
|
+
/**
|
|
311
|
+
* Extract session ID from an event. OpenCode uses different shapes:
|
|
312
|
+
* - session.created/deleted/updated: { properties: { info: { id: string } } }
|
|
313
|
+
* - session.idle/compacted: { properties: { sessionID: string } }
|
|
314
|
+
* - message.part.updated: { properties: { part: { sessionID: string } } }
|
|
315
|
+
*/
|
|
316
|
+
function extractSessionId(event: OpenCodeEvent): string {
|
|
317
|
+
// session.created / session.deleted / session.updated — nested under info
|
|
318
|
+
const info = event.properties.info as OpenCodeSession | undefined;
|
|
319
|
+
if (info?.id) return info.id;
|
|
320
|
+
|
|
321
|
+
// session.idle, session.compacted, tool hooks — direct sessionID
|
|
322
|
+
const sessionID = event.properties.sessionID as string | undefined;
|
|
323
|
+
if (sessionID) return sessionID;
|
|
324
|
+
|
|
325
|
+
// message.part.updated — sessionID on the part object
|
|
326
|
+
const part = event.properties.part as OpenCodePart | undefined;
|
|
327
|
+
if (part && "sessionID" in part && part.sessionID) return part.sessionID;
|
|
328
|
+
|
|
329
|
+
return "unknown";
|
|
330
|
+
}
|
|
331
|
+
|
|
198
332
|
async function handleSessionCreated(
|
|
199
333
|
state: AideState,
|
|
200
334
|
event: OpenCodeEvent,
|
|
201
335
|
): Promise<void> {
|
|
202
|
-
const sessionId = (event
|
|
336
|
+
const sessionId = extractSessionId(event);
|
|
203
337
|
|
|
204
338
|
if (state.initializedSessions.has(sessionId)) return;
|
|
205
339
|
state.initializedSessions.add(sessionId);
|
|
@@ -254,7 +388,7 @@ async function handleSessionIdle(
|
|
|
254
388
|
state: AideState,
|
|
255
389
|
event: OpenCodeEvent,
|
|
256
390
|
): Promise<void> {
|
|
257
|
-
const sessionId = (event
|
|
391
|
+
const sessionId = extractSessionId(event);
|
|
258
392
|
|
|
259
393
|
// Check persistence: if ralph/autopilot mode is active, re-prompt the session
|
|
260
394
|
if (state.binary) {
|
|
@@ -306,10 +440,32 @@ async function handleSessionIdle(
|
|
|
306
440
|
}
|
|
307
441
|
|
|
308
442
|
// Capture session summary (best effort, no transcript available)
|
|
443
|
+
// Uses partials if available for a richer summary.
|
|
309
444
|
if (state.binary) {
|
|
310
|
-
const
|
|
445
|
+
const partials = gatherPartials(state.binary, state.cwd, sessionId);
|
|
446
|
+
let summary: string | null = null;
|
|
447
|
+
|
|
448
|
+
if (partials.length > 0) {
|
|
449
|
+
const commits = getSessionCommits(state.cwd);
|
|
450
|
+
summary = buildSummaryFromPartials(partials, commits, []);
|
|
451
|
+
debug(SOURCE, `Built summary from ${partials.length} partials`);
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
// Fall back to state-only summary if no partials
|
|
455
|
+
if (!summary) {
|
|
456
|
+
summary = buildSessionSummaryFromState(state.cwd);
|
|
457
|
+
}
|
|
458
|
+
|
|
311
459
|
if (summary) {
|
|
312
460
|
storeSessionSummary(state.binary, state.cwd, sessionId, summary);
|
|
461
|
+
// Clean up partials now that the final summary is stored
|
|
462
|
+
const cleaned = cleanupPartials(state.binary, state.cwd, sessionId);
|
|
463
|
+
if (cleaned > 0) {
|
|
464
|
+
debug(SOURCE, `Cleaned up ${cleaned} partials after final summary`);
|
|
465
|
+
}
|
|
466
|
+
} else if (partials.length > 0) {
|
|
467
|
+
// Clean up partials even if no summary was generated
|
|
468
|
+
cleanupPartials(state.binary, state.cwd, sessionId);
|
|
313
469
|
}
|
|
314
470
|
}
|
|
315
471
|
}
|
|
@@ -318,7 +474,7 @@ async function handleSessionDeleted(
|
|
|
318
474
|
state: AideState,
|
|
319
475
|
event: OpenCodeEvent,
|
|
320
476
|
): Promise<void> {
|
|
321
|
-
const sessionId = (event
|
|
477
|
+
const sessionId = extractSessionId(event);
|
|
322
478
|
|
|
323
479
|
if (state.binary) {
|
|
324
480
|
cleanupSession(state.binary, state.cwd, sessionId);
|
|
@@ -333,19 +489,17 @@ async function handleMessagePartUpdated(
|
|
|
333
489
|
state: AideState,
|
|
334
490
|
event: OpenCodeEvent,
|
|
335
491
|
): Promise<void> {
|
|
336
|
-
// Skill injection: only process user
|
|
337
|
-
const part = event.properties.part as
|
|
338
|
-
| { type?: string; text?: string; role?: string }
|
|
339
|
-
| undefined;
|
|
492
|
+
// Skill injection: only process user text parts
|
|
493
|
+
const part = event.properties.part as OpenCodePart | undefined;
|
|
340
494
|
if (!part) return;
|
|
341
495
|
|
|
342
|
-
// Only match skills for
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
if (
|
|
496
|
+
// Only match skills for text parts (user messages)
|
|
497
|
+
if (part.type !== "text") return;
|
|
498
|
+
const textContent = (part as { text?: string }).text;
|
|
499
|
+
if (!textContent) return;
|
|
346
500
|
|
|
347
501
|
// Dedup: don't re-process the same part
|
|
348
|
-
const partId =
|
|
502
|
+
const partId = part.id || "";
|
|
349
503
|
if (partId && state.processedMessageParts.has(partId)) return;
|
|
350
504
|
if (partId) state.processedMessageParts.add(partId);
|
|
351
505
|
|
|
@@ -363,8 +517,13 @@ async function handleMessagePartUpdated(
|
|
|
363
517
|
);
|
|
364
518
|
}
|
|
365
519
|
|
|
366
|
-
const prompt =
|
|
367
|
-
|
|
520
|
+
const prompt = textContent;
|
|
521
|
+
|
|
522
|
+
// Store latest user prompt so system transform can match skills even if
|
|
523
|
+
// this event fires after the transform (defensive against ordering).
|
|
524
|
+
state.lastUserPrompt = prompt;
|
|
525
|
+
|
|
526
|
+
const skills = discoverSkills(state.cwd, state.pluginRoot ?? undefined);
|
|
368
527
|
const matched = matchSkills(prompt, skills, 3);
|
|
369
528
|
|
|
370
529
|
if (matched.length > 0) {
|
|
@@ -377,8 +536,6 @@ async function handleMessagePartUpdated(
|
|
|
377
536
|
message: `Matched ${matched.length} skills: ${matched.map((s) => s.name).join(", ")}`,
|
|
378
537
|
},
|
|
379
538
|
});
|
|
380
|
-
// Note: OpenCode doesn't have a direct skill injection hook like Claude Code's
|
|
381
|
-
// UserPromptSubmit. Skills are logged here; injection happens via system transform.
|
|
382
539
|
// Store matched skills for injection in system transform
|
|
383
540
|
state.pendingSkillsContext = context;
|
|
384
541
|
} catch (err) {
|
|
@@ -470,6 +627,23 @@ function createToolAfterHandler(
|
|
|
470
627
|
|
|
471
628
|
updateToolStats(state.binary, state.cwd, input.tool, input.sessionID);
|
|
472
629
|
|
|
630
|
+
// Write a partial memory for significant tool uses
|
|
631
|
+
try {
|
|
632
|
+
const toolArgs = (_output.metadata?.args || {}) as Record<
|
|
633
|
+
string,
|
|
634
|
+
unknown
|
|
635
|
+
>;
|
|
636
|
+
storePartialMemory(state.binary, state.cwd, {
|
|
637
|
+
toolName: input.tool,
|
|
638
|
+
sessionId: input.sessionID,
|
|
639
|
+
filePath: toolArgs.file_path as string | undefined,
|
|
640
|
+
command: toolArgs.command as string | undefined,
|
|
641
|
+
description: toolArgs.description as string | undefined,
|
|
642
|
+
});
|
|
643
|
+
} catch (err) {
|
|
644
|
+
debug(SOURCE, `Partial memory write failed (non-fatal): ${err}`);
|
|
645
|
+
}
|
|
646
|
+
|
|
473
647
|
// Comment checker: detect excessive comments in Write/Edit output
|
|
474
648
|
try {
|
|
475
649
|
const toolArgs = (_output.metadata?.args || {}) as Record<
|
|
@@ -514,18 +688,40 @@ function createCompactionHandler(
|
|
|
514
688
|
|
|
515
689
|
// Persist a session summary as a memory before context is compacted.
|
|
516
690
|
// This ensures the work-so-far is recoverable after compaction.
|
|
691
|
+
// Uses partials (if available) for a richer summary, falling back to git-only.
|
|
517
692
|
try {
|
|
518
|
-
const
|
|
693
|
+
const partials = gatherPartials(
|
|
694
|
+
state.binary,
|
|
695
|
+
state.cwd,
|
|
696
|
+
input.sessionID,
|
|
697
|
+
);
|
|
698
|
+
let summary: string | null = null;
|
|
699
|
+
|
|
700
|
+
if (partials.length > 0) {
|
|
701
|
+
const commits = getSessionCommits(state.cwd);
|
|
702
|
+
summary = buildSummaryFromPartials(partials, commits, []);
|
|
703
|
+
debug(
|
|
704
|
+
SOURCE,
|
|
705
|
+
`Built pre-compact summary from ${partials.length} partials`,
|
|
706
|
+
);
|
|
707
|
+
}
|
|
708
|
+
|
|
709
|
+
// Fall back to state-only summary if no partials
|
|
710
|
+
if (!summary) {
|
|
711
|
+
summary = buildSessionSummaryFromState(state.cwd);
|
|
712
|
+
}
|
|
713
|
+
|
|
519
714
|
if (summary) {
|
|
520
|
-
|
|
715
|
+
// Tag as partial so the session-end summary supersedes it
|
|
716
|
+
const tags = `partial,session-summary,session:${input.sessionID.slice(0, 8)}`;
|
|
717
|
+
execFileSync(
|
|
521
718
|
state.binary,
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
summary,
|
|
719
|
+
["memory", "add", "--category=session", `--tags=${tags}`, summary],
|
|
720
|
+
{ cwd: state.cwd, stdio: "pipe", timeout: 5000 },
|
|
525
721
|
);
|
|
526
722
|
debug(
|
|
527
723
|
SOURCE,
|
|
528
|
-
`Saved pre-compaction session summary for ${input.sessionID.slice(0, 8)}`,
|
|
724
|
+
`Saved pre-compaction partial session summary for ${input.sessionID.slice(0, 8)}`,
|
|
529
725
|
);
|
|
530
726
|
}
|
|
531
727
|
} catch (err) {
|
|
@@ -629,10 +825,26 @@ function createSystemTransformHandler(
|
|
|
629
825
|
}
|
|
630
826
|
}
|
|
631
827
|
|
|
632
|
-
// Inject
|
|
828
|
+
// Inject matched skills. If message.part.updated already matched skills,
|
|
829
|
+
// use the pre-computed context. Otherwise, match inline from the last user
|
|
830
|
+
// prompt as a fallback (guards against event ordering issues).
|
|
633
831
|
if (state.pendingSkillsContext) {
|
|
634
832
|
output.system.push(state.pendingSkillsContext);
|
|
635
833
|
state.pendingSkillsContext = null;
|
|
834
|
+
} else if (state.lastUserPrompt) {
|
|
835
|
+
try {
|
|
836
|
+
const skills = discoverSkills(state.cwd, state.pluginRoot ?? undefined);
|
|
837
|
+
const matched = matchSkills(state.lastUserPrompt, skills, 3);
|
|
838
|
+
if (matched.length > 0) {
|
|
839
|
+
output.system.push(formatSkillsContext(matched));
|
|
840
|
+
debug(
|
|
841
|
+
SOURCE,
|
|
842
|
+
`System transform fallback matched ${matched.length} skills`,
|
|
843
|
+
);
|
|
844
|
+
}
|
|
845
|
+
} catch (err) {
|
|
846
|
+
debug(SOURCE, `Fallback skill matching failed (non-critical): ${err}`);
|
|
847
|
+
}
|
|
636
848
|
}
|
|
637
849
|
|
|
638
850
|
// Inject messaging protocol for multi-instance coordination
|
package/src/opencode/index.ts
CHANGED
|
@@ -25,12 +25,21 @@
|
|
|
25
25
|
* ```
|
|
26
26
|
*/
|
|
27
27
|
|
|
28
|
+
import { dirname, join } from "path";
|
|
29
|
+
import { fileURLToPath } from "url";
|
|
28
30
|
import { createHooks } from "./hooks.js";
|
|
29
31
|
import type { Plugin, PluginInput, Hooks } from "./types.js";
|
|
30
32
|
|
|
33
|
+
// Resolve the plugin package root so we can find bundled skills.
|
|
34
|
+
// Works whether running from source (repo) or installed via npm.
|
|
35
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
36
|
+
const __dirname = dirname(__filename);
|
|
37
|
+
// index.ts lives in src/opencode/, so the package root is two levels up.
|
|
38
|
+
const pluginRoot = join(__dirname, "..", "..");
|
|
39
|
+
|
|
31
40
|
export const AidePlugin: Plugin = async (ctx: PluginInput): Promise<Hooks> => {
|
|
32
41
|
const cwd = ctx.worktree || ctx.directory;
|
|
33
|
-
return createHooks(cwd, ctx.worktree, ctx.client);
|
|
42
|
+
return createHooks(cwd, ctx.worktree, ctx.client, pluginRoot);
|
|
34
43
|
};
|
|
35
44
|
|
|
36
45
|
export default AidePlugin;
|
package/src/opencode/types.ts
CHANGED
|
@@ -31,7 +31,9 @@ export interface PluginInput {
|
|
|
31
31
|
/** Minimal SDK client interface — only the parts we use */
|
|
32
32
|
export interface OpenCodeClient {
|
|
33
33
|
app: {
|
|
34
|
-
log(opts: {
|
|
34
|
+
log(opts: {
|
|
35
|
+
body: { service: string; level: string; message: string };
|
|
36
|
+
}): Promise<void>;
|
|
35
37
|
};
|
|
36
38
|
session: {
|
|
37
39
|
create(opts: { body: { title?: string } }): Promise<{ id: string }>;
|
|
@@ -54,11 +56,86 @@ export interface OpenCodeClient {
|
|
|
54
56
|
// Events
|
|
55
57
|
// =============================================================================
|
|
56
58
|
|
|
59
|
+
/** Session object as returned in session.created/updated/deleted events */
|
|
60
|
+
export interface OpenCodeSession {
|
|
61
|
+
id: string;
|
|
62
|
+
projectID: string;
|
|
63
|
+
directory: string;
|
|
64
|
+
parentID?: string;
|
|
65
|
+
title: string;
|
|
66
|
+
version: string;
|
|
67
|
+
time: {
|
|
68
|
+
created: number;
|
|
69
|
+
updated: number;
|
|
70
|
+
compacting?: number;
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/** Text part with session context */
|
|
75
|
+
export interface OpenCodeTextPart {
|
|
76
|
+
id: string;
|
|
77
|
+
sessionID: string;
|
|
78
|
+
messageID: string;
|
|
79
|
+
type: "text";
|
|
80
|
+
text: string;
|
|
81
|
+
synthetic?: boolean;
|
|
82
|
+
ignored?: boolean;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/** Generic part — may be text, tool, step, etc. */
|
|
86
|
+
export type OpenCodePart =
|
|
87
|
+
| OpenCodeTextPart
|
|
88
|
+
| {
|
|
89
|
+
id: string;
|
|
90
|
+
sessionID: string;
|
|
91
|
+
messageID: string;
|
|
92
|
+
type: string;
|
|
93
|
+
[key: string]: unknown;
|
|
94
|
+
};
|
|
95
|
+
|
|
57
96
|
export interface OpenCodeEvent {
|
|
58
97
|
type: string;
|
|
59
98
|
properties: Record<string, unknown>;
|
|
60
99
|
}
|
|
61
100
|
|
|
101
|
+
/** Typed event shapes matching the OpenCode SDK */
|
|
102
|
+
export interface EventSessionCreated {
|
|
103
|
+
type: "session.created";
|
|
104
|
+
properties: { info: OpenCodeSession };
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
export interface EventSessionDeleted {
|
|
108
|
+
type: "session.deleted";
|
|
109
|
+
properties: { info: OpenCodeSession };
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
export interface EventSessionIdle {
|
|
113
|
+
type: "session.idle";
|
|
114
|
+
properties: { sessionID: string };
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
export interface EventMessagePartUpdated {
|
|
118
|
+
type: "message.part.updated";
|
|
119
|
+
properties: { part: OpenCodePart; delta?: string };
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
// =============================================================================
|
|
123
|
+
// OpenCode Config (command registration)
|
|
124
|
+
// =============================================================================
|
|
125
|
+
|
|
126
|
+
export interface OpenCodeConfig {
|
|
127
|
+
command?: {
|
|
128
|
+
[key: string]: {
|
|
129
|
+
template: string;
|
|
130
|
+
description?: string;
|
|
131
|
+
agent?: string;
|
|
132
|
+
model?: string;
|
|
133
|
+
subtask?: boolean;
|
|
134
|
+
};
|
|
135
|
+
};
|
|
136
|
+
[key: string]: unknown;
|
|
137
|
+
}
|
|
138
|
+
|
|
62
139
|
// =============================================================================
|
|
63
140
|
// Hook signatures
|
|
64
141
|
// =============================================================================
|
|
@@ -67,6 +144,17 @@ export interface Hooks {
|
|
|
67
144
|
/** Generic event listener for all OpenCode events */
|
|
68
145
|
event?: (input: { event: OpenCodeEvent }) => Promise<void>;
|
|
69
146
|
|
|
147
|
+
/** Modify OpenCode config (register commands, etc.) */
|
|
148
|
+
config?: (input: OpenCodeConfig) => Promise<void>;
|
|
149
|
+
|
|
150
|
+
/** Intercept command execution (slash commands) */
|
|
151
|
+
"command.execute.before"?: (
|
|
152
|
+
input: { command: string; sessionID: string; arguments: string },
|
|
153
|
+
output: {
|
|
154
|
+
parts: Array<{ type: string; text: string; [key: string]: unknown }>;
|
|
155
|
+
},
|
|
156
|
+
) => Promise<void>;
|
|
157
|
+
|
|
70
158
|
/** Modify tool arguments before execution */
|
|
71
159
|
"tool.execute.before"?: (
|
|
72
160
|
input: { tool: string; sessionID: string; callID: string },
|
|
@@ -76,12 +164,19 @@ export interface Hooks {
|
|
|
76
164
|
/** React after tool completes */
|
|
77
165
|
"tool.execute.after"?: (
|
|
78
166
|
input: { tool: string; sessionID: string; callID: string },
|
|
79
|
-
output: {
|
|
167
|
+
output: {
|
|
168
|
+
title: string;
|
|
169
|
+
output: string;
|
|
170
|
+
metadata: Record<string, unknown>;
|
|
171
|
+
},
|
|
80
172
|
) => Promise<void>;
|
|
81
173
|
|
|
82
174
|
/** Modify system prompt */
|
|
83
175
|
"experimental.chat.system.transform"?: (
|
|
84
|
-
input: {
|
|
176
|
+
input: {
|
|
177
|
+
sessionID?: string;
|
|
178
|
+
model: { providerID: string; modelID: string };
|
|
179
|
+
},
|
|
85
180
|
output: { system: string[] },
|
|
86
181
|
) => Promise<void>;
|
|
87
182
|
|