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.
Files changed (2) hide show
  1. package/index.ts +323 -141
  2. 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
- try {
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
- const message = formatCliError(error)
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
- const text = extractText(output.parts).trim()
2174
- if (!text) return
2175
- await writePluginLog(logClient, "info", "AKM user feedback recorded", {
2176
- subsystem: "feedback",
2177
- actor: "user",
2178
- sessionID: input.sessionID,
2179
- messageID: input.messageID,
2180
- agent: input.agent,
2181
- text: truncateLogText(text),
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
- // Compound-engineering loop: on every user message, curate the stash and
2185
- // stash the result so experimental.chat.system.transform can inject it.
2186
- if (AKM_AUTO_CURATE && input.sessionID) {
2187
- const curated = runCurateForPrompt(text, input.sessionID)
2188
- if (curated) {
2189
- sessionCurated.set(input.sessionID, curated)
2190
- bumpCuratedVersion(input.sessionID)
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
- // Track explicit memory intents so capture-memory has something durable
2195
- // to flush when the session ends.
2196
- if (/\b(remember|memory|memories)\b/i.test(text)) {
2197
- addBufferEntry(input.sessionID, {
2198
- kind: "memory-intent",
2199
- note: truncateLogText(text, 500),
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
- if (input.sessionID && AKM_AUTO_FEEDBACK && AKM_RETROSPECTIVE_FEEDBACK_RE.test(text)) {
2204
- const recentRefs = (sessionBuffer.get(input.sessionID) ?? [])
2205
- .filter((entry) => entry.kind === "tool-ref" && !!entry.ref)
2206
- .map((entry) => entry.ref!)
2207
- .filter((ref, index, refs) => !ref.startsWith("memory:") && !ref.startsWith("vault:") && refs.indexOf(ref) === index)
2208
- .slice(-3)
2209
- const dedupe = new Set<string>()
2210
- for (const ref of recentRefs) {
2211
- queueFeedback(logClient, ref, "positive", "opencode retrospective: user confirmed it worked", {
2212
- toolName: "chat.message",
2213
- sessionID: input.sessionID,
2214
- }, dedupe)
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
- if (!input.tool.startsWith("akm_")) return
2368
+ try {
2369
+ if (!input.tool.startsWith("akm_")) return
2221
2370
 
2222
- const parsed = parseToolOutput(output.output)
2223
- if (!parsed) return
2371
+ const parsed = parseToolOutput(output.output)
2372
+ if (!parsed) return
2224
2373
 
2225
- const feedback = classifyToolFeedback(parsed)
2226
- if (feedback) {
2227
- await writePluginLog(logClient, feedback === "negative" ? "warn" : "info", "AKM system feedback recorded", {
2228
- subsystem: "feedback",
2229
- actor: "system",
2230
- feedback,
2231
- toolName: input.tool,
2232
- sessionID: input.sessionID,
2233
- callID: input.callID,
2234
- title: output.title,
2235
- error: typeof (parsed as { error?: unknown }).error === "string" ? (parsed as { error?: string }).error : undefined,
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
- const memoryRefs = extractMemoryRefs(input.tool, input.args as Record<string, unknown>, parsed)
2240
- if (memoryRefs.length > 0) {
2241
- await writePluginLog(logClient, "info", "AKM memory usage recorded", {
2242
- subsystem: "memory",
2243
- toolName: input.tool,
2244
- sessionID: input.sessionID,
2245
- callID: input.callID,
2246
- refs: memoryRefs,
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
- // Auto-feedback + session buffering: record every asset ref the tool
2251
- // touched so the stash ranking improves over time and so Stop/Compact
2252
- // has material to flush into a session summary memory.
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
- for (const ref of refResult.refs) {
2257
- addBufferEntry(input.sessionID, {
2258
- kind: "tool-ref",
2259
- toolName: input.tool,
2260
- ref,
2261
- status: feedback ?? "unknown",
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
- if (
2283
- AKM_AUTO_FEEDBACK
2284
- && feedback
2285
- && input.tool !== "akm_feedback"
2286
- && refResult.refs.length > 0
2287
- ) {
2288
- const dedupe = new Set<string>()
2289
- const feedbackRefs = feedback === "positive"
2290
- ? refResult.refs
2291
- : refResult.refs.filter((ref) => !refResult.positiveOnlyRefs.includes(ref))
2292
- const note = feedback === "positive"
2293
- ? `opencode auto: ${input.tool} succeeded`
2294
- : `opencode auto: ${input.tool} failed`
2295
- for (const ref of feedbackRefs) {
2296
- // Memories and vault refs are not first-class feedback targets —
2297
- // memories do not accept feedback, and vault values never surface in
2298
- // JSON so automatic usage signals would be misleading.
2299
- if (ref.startsWith("memory:") || ref.startsWith("vault:")) continue
2300
- const ok = queueFeedback(logClient, ref, feedback, note, {
2301
- toolName: input.tool,
2302
- sessionID: input.sessionID,
2303
- }, dedupe)
2304
- if (!ok) break
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
- const parent = await getParentSessionID(sdkClient, context.sessionID, context.directory)
2486
- if (!parent.ok) return JSON.stringify(parent)
2487
- const messages = await sdkClient.session.messages({
2488
- path: { id: parent.parentID },
2489
- query: { directory: context.directory },
2490
- })
2491
- if (messages.error || !messages.data) {
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
- const parent = await getParentSessionID(sdkClient, context.sessionID, context.directory)
2504
- const allowedSessionIDs = new Set<string>([context.sessionID])
2505
- if (parent.ok) allowedSessionIDs.add(parent.parentID)
2506
- if (context.agent !== "akm-curator" && !allowedSessionIDs.has(session_id)) {
2507
- return JSON.stringify({
2508
- ok: false,
2509
- error: "akm_session_messages only allows arbitrary session IDs for the akm-curator agent. Use akm_parent_messages for parent context.",
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.1",
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": [