akm-opencode 0.7.1 → 0.7.2
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/index.ts +323 -141
- package/package.json +1 -1
package/index.ts
CHANGED
|
@@ -53,6 +53,7 @@ const sessionSuccessfulAssetTouchCount = new Map<string, number>()
|
|
|
53
53
|
const pendingProposalSummaryCache = new Map<string, { count: number; expiresAt: number; unsupported?: boolean }>()
|
|
54
54
|
const retrospectiveState = new Map<string, { recentRefs: string[]; lastNegativeSignalAt?: number }>()
|
|
55
55
|
let cachedAkmStashDir: string | undefined
|
|
56
|
+
let agentSetupPromise: Promise<boolean> | null = null
|
|
56
57
|
|
|
57
58
|
// Asset-ref grammar matching the stash skill: [origin//]type:name.
|
|
58
59
|
// We validate normalized tokens individually instead of running a global regex
|
|
@@ -218,6 +219,16 @@ function formatCliError(error: unknown): string {
|
|
|
218
219
|
return error instanceof Error ? error.message : String(error)
|
|
219
220
|
}
|
|
220
221
|
|
|
222
|
+
function needsAgentSetup(message: string): boolean {
|
|
223
|
+
return /(agent commands are disabled|agent not configured|no [`'"]?agent[`'"]? block in config\.json)/i.test(message)
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
function addAgentSetupGuidance(message: string): string {
|
|
227
|
+
if (!needsAgentSetup(message)) return message
|
|
228
|
+
if (/\bakm_setup\b|\bakm setup\b/i.test(message)) return message
|
|
229
|
+
return `${message}. Run akm_setup to detect an installed agent CLI and persist agent.default. If none is installed yet, install one of: opencode, claude, codex, gemini, aider.`
|
|
230
|
+
}
|
|
231
|
+
|
|
221
232
|
function toLogString(value: unknown): string | undefined {
|
|
222
233
|
if (typeof value === "string") return value
|
|
223
234
|
if (value instanceof Buffer) return value.toString("utf8")
|
|
@@ -246,6 +257,79 @@ async function writePluginLog(client: LogCapableClient, level: LogLevel, message
|
|
|
246
257
|
}
|
|
247
258
|
}
|
|
248
259
|
|
|
260
|
+
async function runAgentSetup(client: LogCapableClient, meta: { directory?: string; sessionID?: string; trigger: string; force?: boolean }): Promise<boolean> {
|
|
261
|
+
const command = resolveAkmCommand()
|
|
262
|
+
if (typeof command !== "string") {
|
|
263
|
+
await writePluginLog(client, "warn", "AKM agent setup skipped", {
|
|
264
|
+
subsystem: "akm",
|
|
265
|
+
trigger: meta.trigger,
|
|
266
|
+
sessionID: meta.sessionID,
|
|
267
|
+
directory: meta.directory,
|
|
268
|
+
error: command.error,
|
|
269
|
+
})
|
|
270
|
+
return false
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
const args = ["setup"]
|
|
274
|
+
if (meta.force) args.push("--force")
|
|
275
|
+
args.push("--format", "json")
|
|
276
|
+
|
|
277
|
+
try {
|
|
278
|
+
const stdout = execFileSync(command, args, {
|
|
279
|
+
encoding: "utf8",
|
|
280
|
+
timeout: 30_000,
|
|
281
|
+
})
|
|
282
|
+
const parsed = safeJsonParse<{ ok?: boolean; error?: string }>(stdout)
|
|
283
|
+
if (parsed?.ok === false) {
|
|
284
|
+
await writePluginLog(client, "warn", "AKM agent setup reported an error", {
|
|
285
|
+
subsystem: "akm",
|
|
286
|
+
trigger: meta.trigger,
|
|
287
|
+
sessionID: meta.sessionID,
|
|
288
|
+
directory: meta.directory,
|
|
289
|
+
command,
|
|
290
|
+
args,
|
|
291
|
+
stdout,
|
|
292
|
+
error: parsed.error ?? "Unknown akm setup error",
|
|
293
|
+
})
|
|
294
|
+
return false
|
|
295
|
+
}
|
|
296
|
+
await writePluginLog(client, "info", "AKM agent setup completed", {
|
|
297
|
+
subsystem: "akm",
|
|
298
|
+
trigger: meta.trigger,
|
|
299
|
+
sessionID: meta.sessionID,
|
|
300
|
+
directory: meta.directory,
|
|
301
|
+
command,
|
|
302
|
+
args,
|
|
303
|
+
stdout,
|
|
304
|
+
})
|
|
305
|
+
return true
|
|
306
|
+
} catch (error: unknown) {
|
|
307
|
+
const message = addAgentSetupGuidance(formatCliError(error))
|
|
308
|
+
await writePluginLog(client, "warn", "AKM agent setup failed", {
|
|
309
|
+
subsystem: "akm",
|
|
310
|
+
trigger: meta.trigger,
|
|
311
|
+
sessionID: meta.sessionID,
|
|
312
|
+
directory: meta.directory,
|
|
313
|
+
command,
|
|
314
|
+
args,
|
|
315
|
+
error: message,
|
|
316
|
+
stdout: toLogString((error as { stdout?: unknown }).stdout) ?? "",
|
|
317
|
+
stderr: toLogString((error as { stderr?: unknown }).stderr) ?? message,
|
|
318
|
+
})
|
|
319
|
+
return false
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
async function ensureAgentSetup(client: LogCapableClient, meta: { directory?: string; sessionID?: string; trigger: string; force?: boolean }): Promise<boolean> {
|
|
324
|
+
if (agentSetupPromise) return agentSetupPromise
|
|
325
|
+
const promise = runAgentSetup(client, meta)
|
|
326
|
+
const trackedPromise = promise.finally(() => {
|
|
327
|
+
if (agentSetupPromise === trackedPromise) agentSetupPromise = null
|
|
328
|
+
})
|
|
329
|
+
agentSetupPromise = trackedPromise
|
|
330
|
+
return trackedPromise
|
|
331
|
+
}
|
|
332
|
+
|
|
249
333
|
function nowIso(): string {
|
|
250
334
|
return new Date().toISOString()
|
|
251
335
|
}
|
|
@@ -678,6 +762,21 @@ function queueFeedback(
|
|
|
678
762
|
stdio: "ignore",
|
|
679
763
|
},
|
|
680
764
|
)
|
|
765
|
+
child.on("exit", (code, signal) => {
|
|
766
|
+
if ((typeof code === "number" && code !== 0) || signal) {
|
|
767
|
+
void writePluginLog(client, "warn", "AKM auto-feedback failed", {
|
|
768
|
+
subsystem: "feedback",
|
|
769
|
+
toolName: meta.toolName,
|
|
770
|
+
sessionID: meta.sessionID,
|
|
771
|
+
directory: meta.directory,
|
|
772
|
+
ref,
|
|
773
|
+
sentiment,
|
|
774
|
+
error: signal
|
|
775
|
+
? `akm feedback exited via signal ${signal}`
|
|
776
|
+
: `akm feedback exited with code ${code}`,
|
|
777
|
+
})
|
|
778
|
+
}
|
|
779
|
+
})
|
|
681
780
|
child.on("error", (error) => {
|
|
682
781
|
void writePluginLog(client, "warn", "AKM auto-feedback failed", {
|
|
683
782
|
subsystem: "feedback",
|
|
@@ -1314,11 +1413,7 @@ async function runCli(client: LogCapableClient, args: string[], meta: CliLogMeta
|
|
|
1314
1413
|
const fullArgs = args.includes("--format") ? [...args] : [...args, "--format", "json"]
|
|
1315
1414
|
const proposalId = args[0] === "proposal" && typeof args[2] === "string" ? args[2] : null
|
|
1316
1415
|
|
|
1317
|
-
|
|
1318
|
-
const stdout = execFileSync(command, fullArgs, {
|
|
1319
|
-
encoding: "utf8",
|
|
1320
|
-
timeout: 60_000,
|
|
1321
|
-
})
|
|
1416
|
+
const recordSuccess = async (stdout: string): Promise<string> => {
|
|
1322
1417
|
await writePluginLog(client, "info", "AKM command completed", {
|
|
1323
1418
|
subsystem: "akm",
|
|
1324
1419
|
toolName: meta.toolName,
|
|
@@ -1384,8 +1479,45 @@ async function runCli(client: LogCapableClient, args: string[], meta: CliLogMeta
|
|
|
1384
1479
|
})
|
|
1385
1480
|
}
|
|
1386
1481
|
return stdout
|
|
1482
|
+
}
|
|
1483
|
+
|
|
1484
|
+
try {
|
|
1485
|
+
const stdout = execFileSync(command, fullArgs, {
|
|
1486
|
+
encoding: "utf8",
|
|
1487
|
+
timeout: 60_000,
|
|
1488
|
+
})
|
|
1489
|
+
return recordSuccess(stdout)
|
|
1387
1490
|
} catch (error: unknown) {
|
|
1388
|
-
|
|
1491
|
+
let message = formatCliError(error)
|
|
1492
|
+
if (["akm_reflect", "akm_propose"].includes(meta.toolName) && needsAgentSetup(message)) {
|
|
1493
|
+
const setupOk = await ensureAgentSetup(client, {
|
|
1494
|
+
directory: meta.directory,
|
|
1495
|
+
sessionID: meta.sessionID,
|
|
1496
|
+
trigger: `${meta.toolName}:retry`,
|
|
1497
|
+
force: true,
|
|
1498
|
+
})
|
|
1499
|
+
if (setupOk) {
|
|
1500
|
+
try {
|
|
1501
|
+
const stdout = execFileSync(command, fullArgs, {
|
|
1502
|
+
encoding: "utf8",
|
|
1503
|
+
timeout: 60_000,
|
|
1504
|
+
})
|
|
1505
|
+
await writePluginLog(client, "info", "AKM command recovered after setup", {
|
|
1506
|
+
subsystem: "akm",
|
|
1507
|
+
toolName: meta.toolName,
|
|
1508
|
+
sessionID: meta.sessionID,
|
|
1509
|
+
directory: meta.directory,
|
|
1510
|
+
command,
|
|
1511
|
+
args: fullArgs,
|
|
1512
|
+
})
|
|
1513
|
+
return recordSuccess(stdout)
|
|
1514
|
+
} catch (retryError: unknown) {
|
|
1515
|
+
error = retryError
|
|
1516
|
+
message = formatCliError(retryError)
|
|
1517
|
+
}
|
|
1518
|
+
}
|
|
1519
|
+
}
|
|
1520
|
+
message = addAgentSetupGuidance(message)
|
|
1389
1521
|
await writePluginLog(client, "error", "AKM command failed", {
|
|
1390
1522
|
subsystem: "akm",
|
|
1391
1523
|
toolName: meta.toolName,
|
|
@@ -1980,6 +2112,11 @@ export const AkmPlugin: Plugin = async ({ client, worktree, directory }) => {
|
|
|
1980
2112
|
if (!sid) return
|
|
1981
2113
|
if (!sessionContextEpoch.has(sid)) sessionContextEpoch.set(sid, 0)
|
|
1982
2114
|
if (type === "session.created") {
|
|
2115
|
+
await ensureAgentSetup(logClient, {
|
|
2116
|
+
directory,
|
|
2117
|
+
sessionID: sid,
|
|
2118
|
+
trigger: "session.created",
|
|
2119
|
+
})
|
|
1983
2120
|
warmIndexInBackground()
|
|
1984
2121
|
if (AKM_AUTO_CURATE && !sessionCurated.has(sid)) {
|
|
1985
2122
|
const curated = runCurateForSession(sid)
|
|
@@ -2170,139 +2307,161 @@ export const AkmPlugin: Plugin = async ({ client, worktree, directory }) => {
|
|
|
2170
2307
|
}
|
|
2171
2308
|
},
|
|
2172
2309
|
"chat.message": async (input, output) => {
|
|
2173
|
-
|
|
2174
|
-
|
|
2175
|
-
|
|
2176
|
-
|
|
2177
|
-
|
|
2178
|
-
|
|
2179
|
-
|
|
2180
|
-
|
|
2181
|
-
|
|
2182
|
-
|
|
2310
|
+
try {
|
|
2311
|
+
const text = extractText(output.parts).trim()
|
|
2312
|
+
if (!text) return
|
|
2313
|
+
await writePluginLog(logClient, "info", "AKM user feedback recorded", {
|
|
2314
|
+
subsystem: "feedback",
|
|
2315
|
+
actor: "user",
|
|
2316
|
+
sessionID: input.sessionID,
|
|
2317
|
+
messageID: input.messageID,
|
|
2318
|
+
agent: input.agent,
|
|
2319
|
+
text: truncateLogText(text),
|
|
2320
|
+
})
|
|
2183
2321
|
|
|
2184
|
-
|
|
2185
|
-
|
|
2186
|
-
|
|
2187
|
-
|
|
2188
|
-
|
|
2189
|
-
|
|
2190
|
-
|
|
2322
|
+
// Compound-engineering loop: on every user message, curate the stash and
|
|
2323
|
+
// stash the result so experimental.chat.system.transform can inject it.
|
|
2324
|
+
if (AKM_AUTO_CURATE && input.sessionID) {
|
|
2325
|
+
const curated = runCurateForPrompt(text, input.sessionID)
|
|
2326
|
+
if (curated) {
|
|
2327
|
+
sessionCurated.set(input.sessionID, curated)
|
|
2328
|
+
bumpCuratedVersion(input.sessionID)
|
|
2329
|
+
}
|
|
2191
2330
|
}
|
|
2192
|
-
}
|
|
2193
2331
|
|
|
2194
|
-
|
|
2195
|
-
|
|
2196
|
-
|
|
2197
|
-
|
|
2198
|
-
|
|
2199
|
-
|
|
2200
|
-
|
|
2201
|
-
|
|
2332
|
+
// Track explicit memory intents so capture-memory has something durable
|
|
2333
|
+
// to flush when the session ends.
|
|
2334
|
+
if (/\b(remember|memory|memories)\b/i.test(text)) {
|
|
2335
|
+
addBufferEntry(input.sessionID, {
|
|
2336
|
+
kind: "memory-intent",
|
|
2337
|
+
note: truncateLogText(text, 500),
|
|
2338
|
+
})
|
|
2339
|
+
}
|
|
2202
2340
|
|
|
2203
|
-
|
|
2204
|
-
|
|
2205
|
-
|
|
2206
|
-
|
|
2207
|
-
|
|
2208
|
-
|
|
2209
|
-
|
|
2210
|
-
|
|
2211
|
-
|
|
2212
|
-
|
|
2213
|
-
|
|
2214
|
-
|
|
2341
|
+
if (input.sessionID && AKM_AUTO_FEEDBACK && AKM_RETROSPECTIVE_FEEDBACK_RE.test(text)) {
|
|
2342
|
+
const recentRefs = (sessionBuffer.get(input.sessionID) ?? [])
|
|
2343
|
+
.filter((entry) => entry.kind === "tool-ref" && !!entry.ref)
|
|
2344
|
+
.map((entry) => entry.ref!)
|
|
2345
|
+
.filter((ref, index, refs) => !ref.startsWith("memory:") && !ref.startsWith("vault:") && refs.indexOf(ref) === index)
|
|
2346
|
+
.slice(-3)
|
|
2347
|
+
const dedupe = new Set<string>()
|
|
2348
|
+
for (const ref of recentRefs) {
|
|
2349
|
+
queueFeedback(logClient, ref, "positive", "opencode retrospective: user confirmed it worked", {
|
|
2350
|
+
toolName: "chat.message",
|
|
2351
|
+
sessionID: input.sessionID,
|
|
2352
|
+
}, dedupe)
|
|
2353
|
+
}
|
|
2215
2354
|
}
|
|
2355
|
+
await recordRetrospectiveFeedback(logClient, input.sessionID, text)
|
|
2356
|
+
} catch (error: unknown) {
|
|
2357
|
+
await writePluginLog(logClient, "error", "AKM chat.message hook failed", {
|
|
2358
|
+
subsystem: "hook",
|
|
2359
|
+
hook: "chat.message",
|
|
2360
|
+
sessionID: input?.sessionID,
|
|
2361
|
+
messageID: input?.messageID,
|
|
2362
|
+
agent: input?.agent,
|
|
2363
|
+
error: formatCliError(error),
|
|
2364
|
+
})
|
|
2216
2365
|
}
|
|
2217
|
-
await recordRetrospectiveFeedback(logClient, input.sessionID, text)
|
|
2218
2366
|
},
|
|
2219
2367
|
"tool.execute.after": async (input, output) => {
|
|
2220
|
-
|
|
2368
|
+
try {
|
|
2369
|
+
if (!input.tool.startsWith("akm_")) return
|
|
2221
2370
|
|
|
2222
|
-
|
|
2223
|
-
|
|
2371
|
+
const parsed = parseToolOutput(output.output)
|
|
2372
|
+
if (!parsed) return
|
|
2224
2373
|
|
|
2225
|
-
|
|
2226
|
-
|
|
2227
|
-
|
|
2228
|
-
|
|
2229
|
-
|
|
2230
|
-
|
|
2231
|
-
|
|
2232
|
-
|
|
2233
|
-
|
|
2234
|
-
|
|
2235
|
-
|
|
2236
|
-
|
|
2237
|
-
|
|
2374
|
+
const feedback = classifyToolFeedback(parsed)
|
|
2375
|
+
if (feedback) {
|
|
2376
|
+
await writePluginLog(logClient, feedback === "negative" ? "warn" : "info", "AKM system feedback recorded", {
|
|
2377
|
+
subsystem: "feedback",
|
|
2378
|
+
actor: "system",
|
|
2379
|
+
feedback,
|
|
2380
|
+
toolName: input.tool,
|
|
2381
|
+
sessionID: input.sessionID,
|
|
2382
|
+
callID: input.callID,
|
|
2383
|
+
title: output.title,
|
|
2384
|
+
error: typeof (parsed as { error?: unknown }).error === "string" ? (parsed as { error?: string }).error : undefined,
|
|
2385
|
+
})
|
|
2386
|
+
}
|
|
2238
2387
|
|
|
2239
|
-
|
|
2240
|
-
|
|
2241
|
-
|
|
2242
|
-
|
|
2243
|
-
|
|
2244
|
-
|
|
2245
|
-
|
|
2246
|
-
|
|
2247
|
-
|
|
2248
|
-
|
|
2388
|
+
const memoryRefs = extractMemoryRefs(input.tool, input.args as Record<string, unknown>, parsed)
|
|
2389
|
+
if (memoryRefs.length > 0) {
|
|
2390
|
+
await writePluginLog(logClient, "info", "AKM memory usage recorded", {
|
|
2391
|
+
subsystem: "memory",
|
|
2392
|
+
toolName: input.tool,
|
|
2393
|
+
sessionID: input.sessionID,
|
|
2394
|
+
callID: input.callID,
|
|
2395
|
+
refs: memoryRefs,
|
|
2396
|
+
})
|
|
2397
|
+
}
|
|
2249
2398
|
|
|
2250
|
-
|
|
2251
|
-
|
|
2252
|
-
|
|
2399
|
+
// Auto-feedback + session buffering: record every asset ref the tool
|
|
2400
|
+
// touched so the stash ranking improves over time and so Stop/Compact
|
|
2401
|
+
// has material to flush into a session summary memory.
|
|
2253
2402
|
const refResult = extractToolRefs(input.tool, input.args as Record<string, unknown>, parsed)
|
|
2254
2403
|
noteRecentRefs(input.sessionID, refResult.refs)
|
|
2255
2404
|
if (refResult.refs.length > 0 && input.sessionID) {
|
|
2256
|
-
|
|
2257
|
-
|
|
2258
|
-
|
|
2259
|
-
|
|
2260
|
-
|
|
2261
|
-
|
|
2262
|
-
})
|
|
2263
|
-
}
|
|
2264
|
-
if (feedback === "positive") {
|
|
2265
|
-
sessionSuccessfulAssetTouchCount.set(
|
|
2266
|
-
input.sessionID,
|
|
2267
|
-
(sessionSuccessfulAssetTouchCount.get(input.sessionID) ?? 0) + 1,
|
|
2268
|
-
)
|
|
2269
|
-
const checkpointRef = maybeCheckpointSessionMemory(input.sessionID)
|
|
2270
|
-
if (checkpointRef) {
|
|
2271
|
-
await writePluginLog(logClient, "info", "AKM checkpoint memory captured", {
|
|
2272
|
-
subsystem: "memory",
|
|
2273
|
-
actor: "system",
|
|
2274
|
-
sessionID: input.sessionID,
|
|
2275
|
-
reason: "checkpoint",
|
|
2276
|
-
ref: checkpointRef,
|
|
2405
|
+
for (const ref of refResult.refs) {
|
|
2406
|
+
addBufferEntry(input.sessionID, {
|
|
2407
|
+
kind: "tool-ref",
|
|
2408
|
+
toolName: input.tool,
|
|
2409
|
+
ref,
|
|
2410
|
+
status: feedback ?? "unknown",
|
|
2277
2411
|
})
|
|
2278
2412
|
}
|
|
2413
|
+
if (feedback === "positive") {
|
|
2414
|
+
sessionSuccessfulAssetTouchCount.set(
|
|
2415
|
+
input.sessionID,
|
|
2416
|
+
(sessionSuccessfulAssetTouchCount.get(input.sessionID) ?? 0) + 1,
|
|
2417
|
+
)
|
|
2418
|
+
const checkpointRef = maybeCheckpointSessionMemory(input.sessionID)
|
|
2419
|
+
if (checkpointRef) {
|
|
2420
|
+
await writePluginLog(logClient, "info", "AKM checkpoint memory captured", {
|
|
2421
|
+
subsystem: "memory",
|
|
2422
|
+
actor: "system",
|
|
2423
|
+
sessionID: input.sessionID,
|
|
2424
|
+
reason: "checkpoint",
|
|
2425
|
+
ref: checkpointRef,
|
|
2426
|
+
})
|
|
2427
|
+
}
|
|
2428
|
+
}
|
|
2279
2429
|
}
|
|
2280
|
-
}
|
|
2281
2430
|
|
|
2282
|
-
|
|
2283
|
-
|
|
2284
|
-
|
|
2285
|
-
|
|
2286
|
-
|
|
2287
|
-
|
|
2288
|
-
|
|
2289
|
-
|
|
2290
|
-
|
|
2291
|
-
|
|
2292
|
-
|
|
2293
|
-
|
|
2294
|
-
|
|
2295
|
-
|
|
2296
|
-
|
|
2297
|
-
|
|
2298
|
-
|
|
2299
|
-
|
|
2300
|
-
|
|
2301
|
-
|
|
2302
|
-
|
|
2303
|
-
|
|
2304
|
-
|
|
2431
|
+
if (
|
|
2432
|
+
AKM_AUTO_FEEDBACK
|
|
2433
|
+
&& feedback
|
|
2434
|
+
&& input.tool !== "akm_feedback"
|
|
2435
|
+
&& refResult.refs.length > 0
|
|
2436
|
+
) {
|
|
2437
|
+
const dedupe = new Set<string>()
|
|
2438
|
+
const feedbackRefs = feedback === "positive"
|
|
2439
|
+
? refResult.refs
|
|
2440
|
+
: refResult.refs.filter((ref) => !refResult.positiveOnlyRefs.includes(ref))
|
|
2441
|
+
const note = feedback === "positive"
|
|
2442
|
+
? `opencode auto: ${input.tool} succeeded`
|
|
2443
|
+
: `opencode auto: ${input.tool} failed`
|
|
2444
|
+
for (const ref of feedbackRefs) {
|
|
2445
|
+
// Memories and vault refs are not first-class feedback targets —
|
|
2446
|
+
// memories do not accept feedback, and vault values never surface in
|
|
2447
|
+
// JSON so automatic usage signals would be misleading.
|
|
2448
|
+
if (ref.startsWith("memory:") || ref.startsWith("vault:")) continue
|
|
2449
|
+
const ok = queueFeedback(logClient, ref, feedback, note, {
|
|
2450
|
+
toolName: input.tool,
|
|
2451
|
+
sessionID: input.sessionID,
|
|
2452
|
+
}, dedupe)
|
|
2453
|
+
if (!ok) break
|
|
2454
|
+
}
|
|
2305
2455
|
}
|
|
2456
|
+
} catch (error: unknown) {
|
|
2457
|
+
await writePluginLog(logClient, "error", "AKM tool.execute.after hook failed", {
|
|
2458
|
+
subsystem: "hook",
|
|
2459
|
+
hook: "tool.execute.after",
|
|
2460
|
+
toolName: input?.tool,
|
|
2461
|
+
sessionID: input?.sessionID,
|
|
2462
|
+
callID: input?.callID,
|
|
2463
|
+
error: formatCliError(error),
|
|
2464
|
+
})
|
|
2306
2465
|
}
|
|
2307
2466
|
},
|
|
2308
2467
|
tool: {
|
|
@@ -2482,16 +2641,27 @@ export const AkmPlugin: Plugin = async ({ client, worktree, directory }) => {
|
|
|
2482
2641
|
description: "Read compact text summaries of the parent session's messages so a dispatched AKM subagent can inherit upstream context.",
|
|
2483
2642
|
args: {},
|
|
2484
2643
|
async execute(_input, context) {
|
|
2485
|
-
|
|
2486
|
-
|
|
2487
|
-
|
|
2488
|
-
|
|
2489
|
-
|
|
2490
|
-
|
|
2491
|
-
|
|
2644
|
+
try {
|
|
2645
|
+
const parent = await getParentSessionID(sdkClient, context.sessionID, context.directory)
|
|
2646
|
+
if (!parent.ok) return JSON.stringify(parent)
|
|
2647
|
+
const messages = await sdkClient.session.messages({
|
|
2648
|
+
path: { id: parent.parentID },
|
|
2649
|
+
query: { directory: context.directory },
|
|
2650
|
+
})
|
|
2651
|
+
if (messages.error || !messages.data) {
|
|
2652
|
+
return JSON.stringify({ ok: false, error: "Failed to read parent session messages." })
|
|
2653
|
+
}
|
|
2654
|
+
return JSON.stringify(summarizeSessionMessages(parent.parentID, messages.data))
|
|
2655
|
+
} catch (error: unknown) {
|
|
2656
|
+
await writePluginLog(logClient, "error", "AKM parent session read failed", {
|
|
2657
|
+
subsystem: "session",
|
|
2658
|
+
toolName: "akm_parent_messages",
|
|
2659
|
+
sessionID: context.sessionID,
|
|
2660
|
+
directory: context.directory,
|
|
2661
|
+
error: formatCliError(error),
|
|
2662
|
+
})
|
|
2492
2663
|
return JSON.stringify({ ok: false, error: "Failed to read parent session messages." })
|
|
2493
2664
|
}
|
|
2494
|
-
return JSON.stringify(summarizeSessionMessages(parent.parentID, messages.data))
|
|
2495
2665
|
},
|
|
2496
2666
|
}),
|
|
2497
2667
|
akm_session_messages: tool({
|
|
@@ -2500,23 +2670,35 @@ export const AkmPlugin: Plugin = async ({ client, worktree, directory }) => {
|
|
|
2500
2670
|
session_id: tool.schema.string().describe("OpenCode session ID to inspect."),
|
|
2501
2671
|
},
|
|
2502
2672
|
async execute({ session_id }, context) {
|
|
2503
|
-
|
|
2504
|
-
|
|
2505
|
-
|
|
2506
|
-
|
|
2507
|
-
|
|
2508
|
-
|
|
2509
|
-
|
|
2673
|
+
try {
|
|
2674
|
+
const parent = await getParentSessionID(sdkClient, context.sessionID, context.directory)
|
|
2675
|
+
const allowedSessionIDs = new Set<string>([context.sessionID])
|
|
2676
|
+
if (parent.ok) allowedSessionIDs.add(parent.parentID)
|
|
2677
|
+
if (context.agent !== "akm-curator" && !allowedSessionIDs.has(session_id)) {
|
|
2678
|
+
return JSON.stringify({
|
|
2679
|
+
ok: false,
|
|
2680
|
+
error: "akm_session_messages only allows arbitrary session IDs for the akm-curator agent. Use akm_parent_messages for parent context.",
|
|
2681
|
+
})
|
|
2682
|
+
}
|
|
2683
|
+
const messages = await sdkClient.session.messages({
|
|
2684
|
+
path: { id: session_id },
|
|
2685
|
+
query: { directory: context.directory },
|
|
2686
|
+
})
|
|
2687
|
+
if (messages.error || !messages.data) {
|
|
2688
|
+
return JSON.stringify({ ok: false, error: `Failed to read messages for session '${session_id}'.` })
|
|
2689
|
+
}
|
|
2690
|
+
return JSON.stringify(summarizeSessionMessages(session_id, messages.data))
|
|
2691
|
+
} catch (error: unknown) {
|
|
2692
|
+
await writePluginLog(logClient, "error", "AKM session messages read failed", {
|
|
2693
|
+
subsystem: "session",
|
|
2694
|
+
toolName: "akm_session_messages",
|
|
2695
|
+
sessionID: context.sessionID,
|
|
2696
|
+
directory: context.directory,
|
|
2697
|
+
targetSessionID: session_id,
|
|
2698
|
+
error: formatCliError(error),
|
|
2510
2699
|
})
|
|
2511
|
-
}
|
|
2512
|
-
const messages = await sdkClient.session.messages({
|
|
2513
|
-
path: { id: session_id },
|
|
2514
|
-
query: { directory: context.directory },
|
|
2515
|
-
})
|
|
2516
|
-
if (messages.error || !messages.data) {
|
|
2517
2700
|
return JSON.stringify({ ok: false, error: `Failed to read messages for session '${session_id}'.` })
|
|
2518
2701
|
}
|
|
2519
|
-
return JSON.stringify(summarizeSessionMessages(session_id, messages.data))
|
|
2520
2702
|
},
|
|
2521
2703
|
}),
|
|
2522
2704
|
akm_agent: tool({
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "akm-opencode",
|
|
3
|
-
"version": "0.7.
|
|
3
|
+
"version": "0.7.2",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "OpenCode plugin for AKM v0.7.0 - search, show, and manage extension assets via the akm CLI, including vaults, wikis, workflows, the proposal queue, lesson assets, and reflect/propose/distill commands, with agentic hooks that auto-load relevant stash assets, record feedback, and harvest session memories so the stash improves every session.",
|
|
6
6
|
"keywords": [
|