@cortexkit/aft-opencode 0.18.3 → 0.19.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.
Files changed (48) hide show
  1. package/dist/bg-notifications.d.ts.map +1 -1
  2. package/dist/configure-warnings.d.ts +31 -0
  3. package/dist/configure-warnings.d.ts.map +1 -0
  4. package/dist/index.d.ts.map +1 -1
  5. package/dist/index.js +17956 -17699
  6. package/dist/logger.d.ts +18 -0
  7. package/dist/logger.d.ts.map +1 -1
  8. package/dist/notifications.d.ts.map +1 -1
  9. package/dist/shared/last-assistant-model.d.ts +51 -0
  10. package/dist/shared/last-assistant-model.d.ts.map +1 -0
  11. package/dist/shared/rpc-client.d.ts +2 -0
  12. package/dist/shared/rpc-client.d.ts.map +1 -1
  13. package/dist/shared/session-directory.d.ts +32 -0
  14. package/dist/shared/session-directory.d.ts.map +1 -0
  15. package/dist/tools/_shared.d.ts +26 -4
  16. package/dist/tools/_shared.d.ts.map +1 -1
  17. package/dist/tools/bash.d.ts.map +1 -1
  18. package/dist/tools/hoisted.d.ts.map +1 -1
  19. package/dist/tools/reading.d.ts +14 -0
  20. package/dist/tools/reading.d.ts.map +1 -1
  21. package/dist/tui.js +36 -11
  22. package/dist/types.d.ts +1 -1
  23. package/dist/types.d.ts.map +1 -1
  24. package/dist/workflow-hints.d.ts +25 -0
  25. package/dist/workflow-hints.d.ts.map +1 -0
  26. package/package.json +8 -6
  27. package/src/logger.ts +19 -0
  28. package/src/shared/last-assistant-model.ts +179 -0
  29. package/src/shared/rpc-client.ts +38 -5
  30. package/src/shared/session-directory.ts +152 -0
  31. package/dist/bridge.d.ts +0 -122
  32. package/dist/bridge.d.ts.map +0 -1
  33. package/dist/downloader.d.ts +0 -35
  34. package/dist/downloader.d.ts.map +0 -1
  35. package/dist/onnx-runtime.d.ts +0 -53
  36. package/dist/onnx-runtime.d.ts.map +0 -1
  37. package/dist/platform.d.ts +0 -21
  38. package/dist/platform.d.ts.map +0 -1
  39. package/dist/pool.d.ts +0 -64
  40. package/dist/pool.d.ts.map +0 -1
  41. package/dist/resolver.d.ts +0 -36
  42. package/dist/resolver.d.ts.map +0 -1
  43. package/dist/shared/last-user-model.d.ts +0 -38
  44. package/dist/shared/last-user-model.d.ts.map +0 -1
  45. package/dist/shared/url-fetch.d.ts +0 -17
  46. package/dist/shared/url-fetch.d.ts.map +0 -1
  47. package/src/shared/last-user-model.ts +0 -133
  48. package/src/shared/url-fetch.ts +0 -420
package/dist/logger.d.ts CHANGED
@@ -11,4 +11,22 @@ export declare function sessionLog(sessionId: string | undefined, message: strin
11
11
  export declare function sessionWarn(sessionId: string | undefined, message: string, data?: unknown): void;
12
12
  export declare function sessionError(sessionId: string | undefined, message: string, data?: unknown): void;
13
13
  export declare function getLogFilePath(): string;
14
+ /**
15
+ * Adapter that exposes this logger as a {@link import("@cortexkit/aft-bridge").Logger}
16
+ * for the shared bridge package. The bridge package never knows about this log
17
+ * file or the `[aft-plugin]` tag — it just calls `log/warn/error` and we map
18
+ * its `LogMeta.sessionId` into our internal session-prefix shape.
19
+ */
20
+ export declare const bridgeLogger: {
21
+ log(message: string, meta?: {
22
+ sessionId?: string;
23
+ }): void;
24
+ warn(message: string, meta?: {
25
+ sessionId?: string;
26
+ }): void;
27
+ error(message: string, meta?: {
28
+ sessionId?: string;
29
+ }): void;
30
+ getLogFilePath: () => string;
31
+ };
14
32
  //# sourceMappingURL=logger.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"logger.d.ts","sourceRoot":"","sources":["../src/logger.ts"],"names":[],"mappings":"AAuEA,wBAAgB,GAAG,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,OAAO,GAAG,IAAI,CAEzD;AAED,wBAAgB,IAAI,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,OAAO,GAAG,IAAI,CAE1D;AAED,wBAAgB,KAAK,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,OAAO,GAAG,IAAI,CAE3D;AAED;;;;;GAKG;AACH,wBAAgB,UAAU,CAAC,SAAS,EAAE,MAAM,GAAG,SAAS,EAAE,OAAO,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,OAAO,GAAG,IAAI,CAE/F;AAED,wBAAgB,WAAW,CAAC,SAAS,EAAE,MAAM,GAAG,SAAS,EAAE,OAAO,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,OAAO,GAAG,IAAI,CAEhG;AAED,wBAAgB,YAAY,CAAC,SAAS,EAAE,MAAM,GAAG,SAAS,EAAE,OAAO,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,OAAO,GAAG,IAAI,CAEjG;AAED,wBAAgB,cAAc,IAAI,MAAM,CAEvC"}
1
+ {"version":3,"file":"logger.d.ts","sourceRoot":"","sources":["../src/logger.ts"],"names":[],"mappings":"AAuEA,wBAAgB,GAAG,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,OAAO,GAAG,IAAI,CAEzD;AAED,wBAAgB,IAAI,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,OAAO,GAAG,IAAI,CAE1D;AAED,wBAAgB,KAAK,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,OAAO,GAAG,IAAI,CAE3D;AAED;;;;;GAKG;AACH,wBAAgB,UAAU,CAAC,SAAS,EAAE,MAAM,GAAG,SAAS,EAAE,OAAO,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,OAAO,GAAG,IAAI,CAE/F;AAED,wBAAgB,WAAW,CAAC,SAAS,EAAE,MAAM,GAAG,SAAS,EAAE,OAAO,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,OAAO,GAAG,IAAI,CAEhG;AAED,wBAAgB,YAAY,CAAC,SAAS,EAAE,MAAM,GAAG,SAAS,EAAE,OAAO,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,OAAO,GAAG,IAAI,CAEjG;AAED,wBAAgB,cAAc,IAAI,MAAM,CAEvC;AAED;;;;;GAKG;AACH,eAAO,MAAM,YAAY;iBACV,MAAM,SAAS;QAAE,SAAS,CAAC,EAAE,MAAM,CAAA;KAAE;kBAGpC,MAAM,SAAS;QAAE,SAAS,CAAC,EAAE,MAAM,CAAA;KAAE;mBAGpC,MAAM,SAAS;QAAE,SAAS,CAAC,EAAE,MAAM,CAAA;KAAE;;CAIrD,CAAC"}
@@ -1 +1 @@
1
- {"version":3,"file":"notifications.d.ts","sourceRoot":"","sources":["../src/notifications.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AA0OH,MAAM,WAAW,mBAAmB;IAClC,8BAA8B;IAC9B,MAAM,EAAE,OAAO,CAAC;IAChB,iDAAiD;IACjD,SAAS,EAAE,MAAM,CAAC;IAClB,sEAAsE;IACtE,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED,MAAM,WAAW,gBAAgB;IAC/B,IAAI,EAAE,yBAAyB,GAAG,uBAAuB,GAAG,oBAAoB,CAAC;IACjF,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,IAAI,EAAE,MAAM,CAAC;CACd;AAED,MAAM,WAAW,uBAAuB;IACtC,MAAM,EAAE,OAAO,CAAC;IAChB,SAAS,EAAE,MAAM,CAAC;IAClB,UAAU,EAAE,MAAM,CAAC;IACnB,aAAa,EAAE,MAAM,CAAC;IACtB,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAED;;;;GAIG;AACH,wBAAsB,WAAW,CAAC,IAAI,EAAE,mBAAmB,EAAE,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAW3F;AAED;;;;GAIG;AACH,wBAAsB,UAAU,CAAC,IAAI,EAAE,mBAAmB,EAAE,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CA0C1F;AAED;;;;;GAKG;AACH,wBAAsB,uBAAuB,CAC3C,IAAI,EAAE,mBAAmB,EACzB,OAAO,EAAE,MAAM,EACf,QAAQ,EAAE,MAAM,EAAE,EAClB,UAAU,CAAC,EAAE,MAAM,GAClB,OAAO,CAAC,IAAI,CAAC,CAqCf;AAwED,wBAAsB,wBAAwB,CAC5C,IAAI,EAAE,uBAAuB,EAC7B,QAAQ,EAAE,gBAAgB,EAAE,GAC3B,OAAO,CAAC,IAAI,CAAC,CAwBf;AAED;;;GAGG;AACH,wBAAsB,eAAe,CAAC,IAAI,EAAE,mBAAmB,GAAG,OAAO,CAAC,IAAI,CAAC,CA0C9E"}
1
+ {"version":3,"file":"notifications.d.ts","sourceRoot":"","sources":["../src/notifications.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAyOH,MAAM,WAAW,mBAAmB;IAClC,8BAA8B;IAC9B,MAAM,EAAE,OAAO,CAAC;IAChB,iDAAiD;IACjD,SAAS,EAAE,MAAM,CAAC;IAClB,sEAAsE;IACtE,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED,MAAM,WAAW,gBAAgB;IAC/B,IAAI,EAAE,yBAAyB,GAAG,uBAAuB,GAAG,oBAAoB,CAAC;IACjF,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,IAAI,EAAE,MAAM,CAAC;CACd;AAED,MAAM,WAAW,uBAAuB;IACtC,MAAM,EAAE,OAAO,CAAC;IAChB,SAAS,EAAE,MAAM,CAAC;IAClB,UAAU,EAAE,MAAM,CAAC;IACnB,aAAa,EAAE,MAAM,CAAC;IACtB,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAED;;;;GAIG;AACH,wBAAsB,WAAW,CAAC,IAAI,EAAE,mBAAmB,EAAE,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAW3F;AAED;;;;GAIG;AACH,wBAAsB,UAAU,CAAC,IAAI,EAAE,mBAAmB,EAAE,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CA0C1F;AAED;;;;;GAKG;AACH,wBAAsB,uBAAuB,CAC3C,IAAI,EAAE,mBAAmB,EACzB,OAAO,EAAE,MAAM,EACf,QAAQ,EAAE,MAAM,EAAE,EAClB,UAAU,CAAC,EAAE,MAAM,GAClB,OAAO,CAAC,IAAI,CAAC,CAqCf;AAwED,wBAAsB,wBAAwB,CAC5C,IAAI,EAAE,uBAAuB,EAC7B,QAAQ,EAAE,gBAAgB,EAAE,GAC3B,OAAO,CAAC,IAAI,CAAC,CAwBf;AAED;;;GAGG;AACH,wBAAsB,eAAe,CAAC,IAAI,EAAE,mBAAmB,GAAG,OAAO,CAAC,IAAI,CAAC,CA0C9E"}
@@ -0,0 +1,51 @@
1
+ /**
2
+ * Cache-busting fix for prompt prefix cache eviction.
3
+ *
4
+ * PROBLEM: Every PromptInput we send to OpenCode (notifications, idle
5
+ * wakeups, ignored messages) creates a new user message. OpenCode's
6
+ * `createUserMessage` resolves variant relative to the chosen agent. If
7
+ * we don't pass model/variant, defaults take over and bust the provider
8
+ * prefix cache that the previous assistant turn warmed.
9
+ *
10
+ * APPROACH: Mirror what `opencode-xtra` does in production. Read recent
11
+ * messages from `client.session.messages()`, prefer the most recent
12
+ * assistant message, fall back to any role, MERGE across messages so
13
+ * partial fields (e.g. agent set on user message but not assistant) are
14
+ * filled in, and read BOTH the flat shape (`info.providerID`) used by
15
+ * AssistantMessage and the nested shape (`info.model.providerID`) used
16
+ * by UserMessage.
17
+ *
18
+ * IMPORTANT: This context is only meaningful for callers that DO trigger
19
+ * LLM inference (e.g. background-bash idle wakes with `noReply: false`).
20
+ * Callers using `noReply: true` (one-off ignored messages, warnings,
21
+ * announcements) never trigger inference, so they don't need model or
22
+ * variant — the model/variant pass-through there is unnecessary AND has
23
+ * been observed to crash OpenCode under some configurations. Limit
24
+ * model/variant pass-through to wake-style calls.
25
+ */
26
+ export interface ResolvedPromptContext {
27
+ agent?: string;
28
+ model?: {
29
+ providerID: string;
30
+ modelID: string;
31
+ };
32
+ variant?: string;
33
+ }
34
+ /**
35
+ * Read recent messages from the OpenCode session and resolve the most
36
+ * recent assistant prompt context. Falls back to user messages if no
37
+ * assistant has the field. Merges across messages so partial fields are
38
+ * filled in. Returns null if no usable context is found.
39
+ *
40
+ * Mirrors `resolveSessionPromptParams` in `opencode-xtra` (the working
41
+ * reference implementation).
42
+ */
43
+ export declare function resolvePromptContext(client: unknown, sessionId: string): Promise<ResolvedPromptContext | null>;
44
+ export interface LastAssistantModel {
45
+ providerID: string;
46
+ modelID: string;
47
+ variant?: string;
48
+ }
49
+ /** @deprecated Use {@link resolvePromptContext} which also returns agent. */
50
+ export declare function getLastAssistantModel(client: unknown, sessionId: string): Promise<LastAssistantModel | null>;
51
+ //# sourceMappingURL=last-assistant-model.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"last-assistant-model.d.ts","sourceRoot":"","sources":["../../src/shared/last-assistant-model.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AAEH,MAAM,WAAW,qBAAqB;IACpC,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,KAAK,CAAC,EAAE;QAAE,UAAU,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAA;KAAE,CAAC;IAChD,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AA0ED;;;;;;;;GAQG;AACH,wBAAsB,oBAAoB,CACxC,MAAM,EAAE,OAAO,EACf,SAAS,EAAE,MAAM,GAChB,OAAO,CAAC,qBAAqB,GAAG,IAAI,CAAC,CAwCvC;AAID,MAAM,WAAW,kBAAkB;IACjC,UAAU,EAAE,MAAM,CAAC;IACnB,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAED,6EAA6E;AAC7E,wBAAsB,qBAAqB,CACzC,MAAM,EAAE,OAAO,EACf,SAAS,EAAE,MAAM,GAChB,OAAO,CAAC,kBAAkB,GAAG,IAAI,CAAC,CAQpC"}
@@ -6,6 +6,8 @@ export declare class AftRpcClient {
6
6
  constructor(storageDir: string, directory: string);
7
7
  /** Call an RPC method. Retries port resolution if the server isn't ready yet. */
8
8
  call<T = Record<string, unknown>>(method: string, params?: Record<string, unknown>): Promise<T>;
9
+ private callResolved;
10
+ private retryAfterReset;
9
11
  /** Check if the RPC server is reachable. */
10
12
  isAvailable(): Promise<boolean>;
11
13
  private resolvePort;
@@ -1 +1 @@
1
- {"version":3,"file":"rpc-client.d.ts","sourceRoot":"","sources":["../../src/shared/rpc-client.ts"],"names":[],"mappings":"AAQA,qBAAa,YAAY;IACvB,OAAO,CAAC,IAAI,CAAuB;IACnC,OAAO,CAAC,KAAK,CAAuB;IACpC,OAAO,CAAC,YAAY,CAAS;IAC7B,OAAO,CAAC,aAAa,CAAS;gBAElB,UAAU,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM;IAIjD,iFAAiF;IAC3E,IAAI,CAAC,CAAC,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EACpC,MAAM,EAAE,MAAM,EACd,MAAM,GAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAM,GACnC,OAAO,CAAC,CAAC,CAAC;IAoBb,4CAA4C;IACtC,WAAW,IAAI,OAAO,CAAC,OAAO,CAAC;YASvB,WAAW;YAIX,eAAe;IAoC7B,OAAO,CAAC,YAAY;YAuBN,WAAW;YAWX,gBAAgB;IAU9B,KAAK,IAAI,IAAI;CAKd"}
1
+ {"version":3,"file":"rpc-client.d.ts","sourceRoot":"","sources":["../../src/shared/rpc-client.ts"],"names":[],"mappings":"AAQA,qBAAa,YAAY;IACvB,OAAO,CAAC,IAAI,CAAuB;IACnC,OAAO,CAAC,KAAK,CAAuB;IACpC,OAAO,CAAC,YAAY,CAAS;IAC7B,OAAO,CAAC,aAAa,CAAS;gBAElB,UAAU,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM;IAIjD,iFAAiF;IAC3E,IAAI,CAAC,CAAC,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EACpC,MAAM,EAAE,MAAM,EACd,MAAM,GAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAM,GACnC,OAAO,CAAC,CAAC,CAAC;YASC,YAAY;YA6BZ,eAAe;IAe7B,4CAA4C;IACtC,WAAW,IAAI,OAAO,CAAC,OAAO,CAAC;YASvB,WAAW;YAIX,eAAe;IAoC7B,OAAO,CAAC,YAAY;YAuBN,WAAW;YAWX,gBAAgB;IAU9B,KAAK,IAAI,IAAI;CAKd"}
@@ -0,0 +1,32 @@
1
+ /**
2
+ * Resolve the project directory the session was created with. Returns
3
+ * `null` when the lookup is unavailable or fails — callers should fall
4
+ * back to the runtime's directory in that case.
5
+ *
6
+ * This function is best-effort: any error (missing `client.session.get`,
7
+ * network failure, malformed response) is logged and recorded as a
8
+ * negative cache entry so we don't retry on every tool call within the
9
+ * same session.
10
+ */
11
+ export declare function getSessionDirectory(client: unknown, sessionId: string, fallbackDirectory: string): Promise<string | null>;
12
+ /**
13
+ * Synchronous cache probe. Returns the resolved directory for a session if we
14
+ * already looked it up; otherwise `undefined` so the caller falls through to
15
+ * its synchronous fallback (typically `runtime.directory`).
16
+ *
17
+ * This is the hot path: `bridgeFor()` runs on every tool call and must not
18
+ * block on an SDK round-trip. The async {@link warmSessionDirectory} should
19
+ * be called eagerly (without await) at the start of each tool call to keep
20
+ * the cache filled, so by the time a second call from the same session
21
+ * arrives, this probe returns the correct directory.
22
+ */
23
+ export declare function getSessionDirectoryCached(sessionId: string | undefined): string | null | undefined;
24
+ /**
25
+ * Fire-and-forget cache warmup. Safe to call from synchronous code; failures
26
+ * are logged but not propagated. Subsequent calls to {@link getSessionDirectoryCached}
27
+ * will return the resolved directory once the lookup completes.
28
+ */
29
+ export declare function warmSessionDirectory(client: unknown, sessionId: string | undefined, fallbackDirectory: string): void;
30
+ /** Test-only: clear the cache between unit tests. */
31
+ export declare function _resetSessionDirectoryCacheForTest(): void;
32
+ //# sourceMappingURL=session-directory.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"session-directory.d.ts","sourceRoot":"","sources":["../../src/shared/session-directory.ts"],"names":[],"mappings":"AA8CA;;;;;;;;;GASG;AACH,wBAAsB,mBAAmB,CACvC,MAAM,EAAE,OAAO,EACf,SAAS,EAAE,MAAM,EACjB,iBAAiB,EAAE,MAAM,GACxB,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CA0CxB;AAWD;;;;;;;;;;GAUG;AACH,wBAAgB,yBAAyB,CACvC,SAAS,EAAE,MAAM,GAAG,SAAS,GAC5B,MAAM,GAAG,IAAI,GAAG,SAAS,CAK3B;AAED;;;;GAIG;AACH,wBAAgB,oBAAoB,CAClC,MAAM,EAAE,OAAO,EACf,SAAS,EAAE,MAAM,GAAG,SAAS,EAC7B,iBAAiB,EAAE,MAAM,GACxB,IAAI,CAIN;AAED,qDAAqD;AACrD,wBAAgB,kCAAkC,IAAI,IAAI,CAEzD"}
@@ -15,7 +15,7 @@
15
15
  * `bridge.send()` multiple times with shared state) should use `bridgeFor()`
16
16
  * and still pass `session_id` explicitly.
17
17
  */
18
- import type { BinaryBridge, BridgeRequestOptions } from "../bridge.js";
18
+ import type { BinaryBridge, BridgeRequestOptions } from "@cortexkit/aft-bridge";
19
19
  import type { PluginContext } from "../types.js";
20
20
  /**
21
21
  * Per-command timeout overrides (milliseconds).
@@ -49,14 +49,25 @@ export interface ToolRuntime {
49
49
  * Prefers `worktree` because that stays stable across OpenCode sessions in
50
50
  * the same project; falls back to `directory` when unavailable (standalone
51
51
  * CLI use, older hosts). Normalizes trailing slashes and resolves symlinks
52
- * via `realpath` so `/repo` and `/repo/` and `/Users/.../repo -> /Volumes/...`
53
- * collapse to the same key.
52
+ * so `/repo` and `/repo/` and `/Users/.../repo -> /Volumes/...` collapse to
53
+ * the same key.
54
+ *
55
+ * NOTE: When the runtime carries a `sessionID` and we have a cached
56
+ * session-stored directory for it (see `shared/session-directory.ts`), the
57
+ * stored directory wins. This is the workaround for OpenCode's bug where
58
+ * `ctx.directory` is set to `process.cwd()` rather than the resumed
59
+ * session's actual project directory.
54
60
  */
55
61
  export declare function projectRootFor(runtime: ToolRuntime): string;
56
62
  /**
57
63
  * Get the BinaryBridge for the runtime's project root.
58
64
  *
59
65
  * Prefer `callBridge()` unless you need to send multiple requests yourself.
66
+ *
67
+ * This is synchronous and uses only the cached session directory. If the
68
+ * cache is cold, it falls back to `runtime.directory` — `callBridge()`
69
+ * eagerly warms the cache before calling this so the cache is hot for
70
+ * subsequent calls in the same session.
60
71
  */
61
72
  export declare function bridgeFor(ctx: PluginContext, runtime: ToolRuntime): BinaryBridge;
62
73
  /**
@@ -66,9 +77,20 @@ export declare function bridgeFor(ctx: PluginContext, runtime: ToolRuntime): Bin
66
77
  * the right bridge (project-keyed), attaches the session namespace from
67
78
  * `context.sessionID`, and returns whatever the binary responds.
68
79
  *
80
+ * Before routing, it ensures the session-directory cache is warm so the
81
+ * very first tool call on a resumed-from-wrong-cwd session still reaches
82
+ * the correct project bridge. Subsequent calls hit the cache synchronously.
83
+ *
69
84
  * The Rust side falls back to a shared default namespace when `session_id`
70
85
  * is absent (see `RawRequest::session()`), so hosts that don't expose a
71
86
  * session identifier still work — they just share undo/checkpoint state.
72
87
  */
73
- export declare function callBridge(ctx: PluginContext, runtime: ToolRuntime, command: string, params?: Record<string, unknown>, options?: BridgeRequestOptions): ReturnType<BinaryBridge["send"]>;
88
+ export declare function callBridge(ctx: PluginContext, runtime: ToolRuntime, command: string, params?: Record<string, unknown>, options?: BridgeRequestOptions): Promise<Record<string, unknown>>;
89
+ /**
90
+ * Eagerly warm the session-directory cache for a runtime. Safe to call from
91
+ * synchronous code — the lookup runs in the background and failures are
92
+ * logged. Useful in plugin lifecycle hooks (`chat.message`, etc.) where we
93
+ * want the cache filled before any tool call arrives.
94
+ */
95
+ export declare function warmSessionDirectoryFromRuntime(ctx: PluginContext, runtime: ToolRuntime): void;
74
96
  //# sourceMappingURL=_shared.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"_shared.d.ts","sourceRoot":"","sources":["../../src/tools/_shared.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAKH,OAAO,KAAK,EAAE,YAAY,EAAE,oBAAoB,EAAE,MAAM,cAAc,CAAC;AACvE,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAEjD;;;;;;;GAOG;AACH,eAAO,MAAM,+BAA+B,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAQlE,CAAC;AAEF,wFAAwF;AACxF,wBAAgB,iBAAiB,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAErE;AAED;;;;;;GAMG;AACH,MAAM,WAAW,WAAW;IAC1B,wEAAwE;IACxE,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,oDAAoD;IACpD,SAAS,EAAE,MAAM,CAAC;IAClB,6EAA6E;IAC7E,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED;;;;;;;;GAQG;AACH,wBAAgB,cAAc,CAAC,OAAO,EAAE,WAAW,GAAG,MAAM,CAa3D;AAED;;;;GAIG;AACH,wBAAgB,SAAS,CAAC,GAAG,EAAE,aAAa,EAAE,OAAO,EAAE,WAAW,GAAG,YAAY,CAEhF;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,UAAU,CACxB,GAAG,EAAE,aAAa,EAClB,OAAO,EAAE,WAAW,EACpB,OAAO,EAAE,MAAM,EACf,MAAM,GAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAM,EACpC,OAAO,CAAC,EAAE,oBAAoB,GAC7B,UAAU,CAAC,YAAY,CAAC,MAAM,CAAC,CAAC,CAiBlC"}
1
+ {"version":3,"file":"_shared.d.ts","sourceRoot":"","sources":["../../src/tools/_shared.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAIH,OAAO,KAAK,EAAE,YAAY,EAAE,oBAAoB,EAAE,MAAM,uBAAuB,CAAC;AAOhF,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAEjD;;;;;;;GAOG;AACH,eAAO,MAAM,+BAA+B,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAQlE,CAAC;AAEF,wFAAwF;AACxF,wBAAgB,iBAAiB,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAErE;AAED;;;;;;GAMG;AACH,MAAM,WAAW,WAAW;IAC1B,wEAAwE;IACxE,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,oDAAoD;IACpD,SAAS,EAAE,MAAM,CAAC;IAClB,6EAA6E;IAC7E,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAkBD;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,cAAc,CAAC,OAAO,EAAE,WAAW,GAAG,MAAM,CAW3D;AAED;;;;;;;;;GASG;AACH,wBAAgB,SAAS,CAAC,GAAG,EAAE,aAAa,EAAE,OAAO,EAAE,WAAW,GAAG,YAAY,CAEhF;AAED;;;;;;;;;;;;;;GAcG;AACH,wBAAsB,UAAU,CAC9B,GAAG,EAAE,aAAa,EAClB,OAAO,EAAE,WAAW,EACpB,OAAO,EAAE,MAAM,EACf,MAAM,GAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAM,EACpC,OAAO,CAAC,EAAE,oBAAoB,GAC7B,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CA0BlC;AAED;;;;;GAKG;AACH,wBAAgB,+BAA+B,CAAC,GAAG,EAAE,aAAa,EAAE,OAAO,EAAE,WAAW,GAAG,IAAI,CAE9F"}
@@ -1 +1 @@
1
- {"version":3,"file":"bash.d.ts","sourceRoot":"","sources":["../../src/tools/bash.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAe,cAAc,EAAE,MAAM,qBAAqB,CAAC;AAKvE,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAsDjD,wBAAgB,cAAc,CAAC,GAAG,EAAE,aAAa,GAAG,cAAc,CAoJjE;AAED,wBAAgB,oBAAoB,CAAC,GAAG,EAAE,aAAa,GAAG,cAAc,CA4BvE;AAED,wBAAgB,kBAAkB,CAAC,GAAG,EAAE,aAAa,GAAG,cAAc,CAmBrE"}
1
+ {"version":3,"file":"bash.d.ts","sourceRoot":"","sources":["../../src/tools/bash.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAe,cAAc,EAAE,MAAM,qBAAqB,CAAC;AAIvE,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAsDjD,wBAAgB,cAAc,CAAC,GAAG,EAAE,aAAa,GAAG,cAAc,CAoJjE;AAED,wBAAgB,oBAAoB,CAAC,GAAG,EAAE,aAAa,GAAG,cAAc,CA4BvE;AAED,wBAAgB,kBAAkB,CAAC,GAAG,EAAE,aAAa,GAAG,cAAc,CAmBrE"}
@@ -1 +1 @@
1
- {"version":3,"file":"hoisted.d.ts","sourceRoot":"","sources":["../../src/tools/hoisted.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAIH,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC;AAI1D,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAkEjD,wEAAwE;AACxE,eAAO,MAAM,wBAAwB,GAAI,IAAI,MAAM,EAAE,QAAQ,MAAM,EAAE,OAAO,MAAM,KAAG,MAChD,CAAC;AA+QtC;;GAEG;AACH,wBAAgB,cAAc,CAAC,GAAG,EAAE,aAAa,GAAG,cAAc,CAgJjE;AAm+BD;;;GAGG;AACH,wBAAgB,YAAY,CAAC,GAAG,EAAE,aAAa,GAAG,MAAM,CAAC,MAAM,EAAE,cAAc,CAAC,CAY/E;AAMD;;GAEG;AACH,wBAAgB,gBAAgB,CAAC,GAAG,EAAE,aAAa,GAAG,MAAM,CAAC,MAAM,EAAE,cAAc,CAAC,CAmEnF"}
1
+ {"version":3,"file":"hoisted.d.ts","sourceRoot":"","sources":["../../src/tools/hoisted.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAIH,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC;AAI1D,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAkEjD,wEAAwE;AACxE,eAAO,MAAM,wBAAwB,GAAI,IAAI,MAAM,EAAE,QAAQ,MAAM,EAAE,OAAO,MAAM,KAAG,MAChD,CAAC;AAqRtC;;GAEG;AACH,wBAAgB,cAAc,CAAC,GAAG,EAAE,aAAa,GAAG,cAAc,CAgJjE;AAqgCD;;;GAGG;AACH,wBAAgB,YAAY,CAAC,GAAG,EAAE,aAAa,GAAG,MAAM,CAAC,MAAM,EAAE,cAAc,CAAC,CAY/E;AAMD;;GAEG;AACH,wBAAgB,gBAAgB,CAAC,GAAG,EAAE,aAAa,GAAG,MAAM,CAAC,MAAM,EAAE,cAAc,CAAC,CAmEnF"}
@@ -1,7 +1,21 @@
1
1
  import type { ToolDefinition } from "@opencode-ai/plugin";
2
2
  import type { PluginContext } from "../types.js";
3
+ interface ZoomBatchSymbolResult {
4
+ name: string;
5
+ success: boolean;
6
+ content?: string;
7
+ error?: string;
8
+ }
9
+ interface ZoomBatchResult {
10
+ complete: boolean;
11
+ symbols: ZoomBatchSymbolResult[];
12
+ text: string;
13
+ }
3
14
  /**
4
15
  * Tool definitions for code reading commands: outline + zoom.
5
16
  */
6
17
  export declare function readingTools(ctx: PluginContext): Record<string, ToolDefinition>;
18
+ /** Exported for regression tests. */
19
+ export declare function formatZoomBatchResult(symbols: string[], responses: Record<string, unknown>[]): ZoomBatchResult;
20
+ export {};
7
21
  //# sourceMappingURL=reading.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"reading.d.ts","sourceRoot":"","sources":["../../src/tools/reading.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC;AAG1D,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AA2CjD;;GAEG;AACH,wBAAgB,YAAY,CAAC,GAAG,EAAE,aAAa,GAAG,MAAM,CAAC,MAAM,EAAE,cAAc,CAAC,CAuK/E"}
1
+ {"version":3,"file":"reading.d.ts","sourceRoot":"","sources":["../../src/tools/reading.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC;AAE1D,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAKjD,UAAU,qBAAqB;IAC7B,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,OAAO,CAAC;IACjB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED,UAAU,eAAe;IACvB,QAAQ,EAAE,OAAO,CAAC;IAClB,OAAO,EAAE,qBAAqB,EAAE,CAAC;IACjC,IAAI,EAAE,MAAM,CAAC;CACd;AAED;;GAEG;AACH,wBAAgB,YAAY,CAAC,GAAG,EAAE,aAAa,GAAG,MAAM,CAAC,MAAM,EAAE,cAAc,CAAC,CAwJ/E;AAED,qCAAqC;AACrC,wBAAgB,qBAAqB,CACnC,OAAO,EAAE,MAAM,EAAE,EACjB,SAAS,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,GACnC,eAAe,CA0BjB"}
package/dist/tui.js CHANGED
@@ -2,7 +2,7 @@
2
2
  // package.json
3
3
  var package_default = {
4
4
  name: "@cortexkit/aft-opencode",
5
- version: "0.18.3",
5
+ version: "0.19.0",
6
6
  type: "module",
7
7
  description: "OpenCode plugin for Agent File Tools (AFT) \u2014 tree-sitter and lsp powered code analysis",
8
8
  main: "dist/index.js",
@@ -30,17 +30,19 @@ var package_default = {
30
30
  },
31
31
  dependencies: {
32
32
  "@clack/prompts": "^1.2.0",
33
+ "@cortexkit/aft-bridge": "0.19.0",
33
34
  "@opencode-ai/plugin": "^1.2.26",
34
35
  "@opencode-ai/sdk": "^1.2.26",
35
36
  "comment-json": "^4.6.2",
37
+ undici: "^7.25.0",
36
38
  zod: "^4.1.8"
37
39
  },
38
40
  optionalDependencies: {
39
- "@cortexkit/aft-darwin-arm64": "0.18.3",
40
- "@cortexkit/aft-darwin-x64": "0.18.3",
41
- "@cortexkit/aft-linux-arm64": "0.18.3",
42
- "@cortexkit/aft-linux-x64": "0.18.3",
43
- "@cortexkit/aft-win32-x64": "0.18.3"
41
+ "@cortexkit/aft-darwin-arm64": "0.19.0",
42
+ "@cortexkit/aft-darwin-x64": "0.19.0",
43
+ "@cortexkit/aft-linux-arm64": "0.19.0",
44
+ "@cortexkit/aft-linux-x64": "0.19.0",
45
+ "@cortexkit/aft-win32-x64": "0.19.0"
44
46
  },
45
47
  devDependencies: {
46
48
  "@types/node": "^22.0.0",
@@ -157,17 +159,40 @@ class AftRpcClient {
157
159
  if (!info) {
158
160
  throw new Error("AFT RPC server not available");
159
161
  }
160
- const response = await this.fetchWithTimeout(`http://127.0.0.1:${info.port}/rpc/${method}`, {
161
- method: "POST",
162
- headers: { "Content-Type": "application/json" },
163
- body: JSON.stringify({ ...params, token: info.token })
164
- });
162
+ return this.callResolved(method, params, info, true);
163
+ }
164
+ async callResolved(method, params, info, retryOnConnectionFailure) {
165
+ let response;
166
+ try {
167
+ response = await this.fetchWithTimeout(`http://127.0.0.1:${info.port}/rpc/${method}`, {
168
+ method: "POST",
169
+ headers: { "Content-Type": "application/json" },
170
+ body: JSON.stringify({ ...params, token: info.token })
171
+ });
172
+ } catch (err) {
173
+ if (!retryOnConnectionFailure)
174
+ throw err;
175
+ return this.retryAfterReset(method, params, err);
176
+ }
165
177
  if (!response.ok) {
166
178
  const text = await response.text();
179
+ if (response.status >= 500 && retryOnConnectionFailure) {
180
+ return this.retryAfterReset(method, params, `HTTP ${response.status}: ${text}`);
181
+ }
167
182
  throw new Error(`RPC ${method} failed (${response.status}): ${text}`);
168
183
  }
169
184
  return await response.json();
170
185
  }
186
+ async retryAfterReset(method, params, reason) {
187
+ const message = reason instanceof Error ? reason.message : String(reason);
188
+ warn(`RPC ${method} failed on cached port; retrying after port refresh (${message})`);
189
+ this.reset();
190
+ const refreshed = await this.resolvePortInfo();
191
+ if (!refreshed) {
192
+ throw new Error("AFT RPC server not available after port refresh");
193
+ }
194
+ return this.callResolved(method, params, refreshed, false);
195
+ }
171
196
  async isAvailable() {
172
197
  try {
173
198
  const port = await this.resolvePort();
package/dist/types.d.ts CHANGED
@@ -1,6 +1,6 @@
1
+ import type { BridgePool } from "@cortexkit/aft-bridge";
1
2
  import type { PluginInput } from "@opencode-ai/plugin";
2
3
  import type { AftConfig } from "./config.js";
3
- import type { BridgePool } from "./pool.js";
4
4
  interface ShellEnvPluginHost {
5
5
  trigger?: (name: "shell.env", context: {
6
6
  cwd: string;
@@ -1 +1 @@
1
- {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,qBAAqB,CAAC;AACvD,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AAC7C,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,WAAW,CAAC;AAE5C,UAAU,kBAAkB;IAC1B,OAAO,CAAC,EAAE,CACR,IAAI,EAAE,WAAW,EACjB,OAAO,EAAE;QAAE,GAAG,EAAE,MAAM,CAAC;QAAC,SAAS,CAAC,EAAE,MAAM,CAAC;QAAC,MAAM,CAAC,EAAE,MAAM,CAAA;KAAE,EAC7D,KAAK,EAAE;QAAE,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;KAAE,KACnC,OAAO,CAAC;QAAE,GAAG,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;KAAE,CAAC,GAAG;QAAE,GAAG,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;KAAE,CAAC;CACnF;AAED;;;GAGG;AACH,MAAM,WAAW,aAAa;IAC5B,IAAI,EAAE,UAAU,CAAC;IACjB,MAAM,EAAE,WAAW,CAAC,QAAQ,CAAC,CAAC;IAC9B,MAAM,CAAC,EAAE,kBAAkB,CAAC;IAC5B,MAAM,EAAE,SAAS,CAAC;IAClB,2FAA2F;IAC3F,UAAU,EAAE,MAAM,CAAC;CACpB"}
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,uBAAuB,CAAC;AACxD,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,qBAAqB,CAAC;AACvD,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AAE7C,UAAU,kBAAkB;IAC1B,OAAO,CAAC,EAAE,CACR,IAAI,EAAE,WAAW,EACjB,OAAO,EAAE;QAAE,GAAG,EAAE,MAAM,CAAC;QAAC,SAAS,CAAC,EAAE,MAAM,CAAC;QAAC,MAAM,CAAC,EAAE,MAAM,CAAA;KAAE,EAC7D,KAAK,EAAE;QAAE,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;KAAE,KACnC,OAAO,CAAC;QAAE,GAAG,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;KAAE,CAAC,GAAG;QAAE,GAAG,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;KAAE,CAAC;CACnF;AAED;;;GAGG;AACH,MAAM,WAAW,aAAa;IAC5B,IAAI,EAAE,UAAU,CAAC;IACjB,MAAM,EAAE,WAAW,CAAC,QAAQ,CAAC,CAAC;IAC9B,MAAM,CAAC,EAAE,kBAAkB,CAAC;IAC5B,MAAM,EAAE,SAAS,CAAC;IAClB,2FAA2F;IAC3F,UAAU,EAAE,MAAM,CAAC;CACpB"}
@@ -0,0 +1,25 @@
1
+ import type { AftConfig } from "./config.js";
2
+ export interface WorkflowHintsOpts {
3
+ /** `tool_surface` setting — controls which tools are registered. */
4
+ toolSurface: "minimal" | "recommended" | "all";
5
+ /** `hoist_builtin_tools` setting — affects tool name (read vs aft_read). */
6
+ hoistBuiltins: boolean;
7
+ /** `experimental.semantic_search` — gates `aft_search` mention. */
8
+ semanticEnabled: boolean;
9
+ /** `experimental.bash.background` — gates background-bash paragraph. */
10
+ bashBackgroundEnabled: boolean;
11
+ /** Set of disabled tool names (after surface filtering). */
12
+ disabledTools: Set<string>;
13
+ }
14
+ /**
15
+ * Build the workflow hints block. Returns `null` when no hints are
16
+ * applicable for the configured surface (e.g. `tool_surface: "minimal"`
17
+ * with no aft_outline/aft_zoom available — only safety tool is registered).
18
+ */
19
+ export declare function buildWorkflowHints(opts: WorkflowHintsOpts): string | null;
20
+ /**
21
+ * Resolve workflow-hints opts from a loaded AftConfig and the active
22
+ * disabled-tools set computed at registration time.
23
+ */
24
+ export declare function buildHintsFromConfig(config: AftConfig, disabledTools: Set<string>): string | null;
25
+ //# sourceMappingURL=workflow-hints.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"workflow-hints.d.ts","sourceRoot":"","sources":["../src/workflow-hints.ts"],"names":[],"mappings":"AAUA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AAE7C,MAAM,WAAW,iBAAiB;IAChC,oEAAoE;IACpE,WAAW,EAAE,SAAS,GAAG,aAAa,GAAG,KAAK,CAAC;IAC/C,4EAA4E;IAC5E,aAAa,EAAE,OAAO,CAAC;IACvB,mEAAmE;IACnE,eAAe,EAAE,OAAO,CAAC;IACzB,wEAAwE;IACxE,qBAAqB,EAAE,OAAO,CAAC;IAC/B,4DAA4D;IAC5D,aAAa,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC;CAC5B;AAID;;;;GAIG;AACH,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,iBAAiB,GAAG,MAAM,GAAG,IAAI,CA2EzE;AAED;;;GAGG;AACH,wBAAgB,oBAAoB,CAAC,MAAM,EAAE,SAAS,EAAE,aAAa,EAAE,GAAG,CAAC,MAAM,CAAC,GAAG,MAAM,GAAG,IAAI,CAQjG"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cortexkit/aft-opencode",
3
- "version": "0.18.3",
3
+ "version": "0.19.0",
4
4
  "type": "module",
5
5
  "description": "OpenCode plugin for Agent File Tools (AFT) — tree-sitter and lsp powered code analysis",
6
6
  "main": "dist/index.js",
@@ -28,17 +28,19 @@
28
28
  },
29
29
  "dependencies": {
30
30
  "@clack/prompts": "^1.2.0",
31
+ "@cortexkit/aft-bridge": "0.19.0",
31
32
  "@opencode-ai/plugin": "^1.2.26",
32
33
  "@opencode-ai/sdk": "^1.2.26",
33
34
  "comment-json": "^4.6.2",
35
+ "undici": "^7.25.0",
34
36
  "zod": "^4.1.8"
35
37
  },
36
38
  "optionalDependencies": {
37
- "@cortexkit/aft-darwin-arm64": "0.18.3",
38
- "@cortexkit/aft-darwin-x64": "0.18.3",
39
- "@cortexkit/aft-linux-arm64": "0.18.3",
40
- "@cortexkit/aft-linux-x64": "0.18.3",
41
- "@cortexkit/aft-win32-x64": "0.18.3"
39
+ "@cortexkit/aft-darwin-arm64": "0.19.0",
40
+ "@cortexkit/aft-darwin-x64": "0.19.0",
41
+ "@cortexkit/aft-linux-arm64": "0.19.0",
42
+ "@cortexkit/aft-linux-x64": "0.19.0",
43
+ "@cortexkit/aft-win32-x64": "0.19.0"
42
44
  },
43
45
  "devDependencies": {
44
46
  "@types/node": "^22.0.0",
package/src/logger.ts CHANGED
@@ -102,3 +102,22 @@ export function sessionError(sessionId: string | undefined, message: string, dat
102
102
  export function getLogFilePath(): string {
103
103
  return logFile;
104
104
  }
105
+
106
+ /**
107
+ * Adapter that exposes this logger as a {@link import("@cortexkit/aft-bridge").Logger}
108
+ * for the shared bridge package. The bridge package never knows about this log
109
+ * file or the `[aft-plugin]` tag — it just calls `log/warn/error` and we map
110
+ * its `LogMeta.sessionId` into our internal session-prefix shape.
111
+ */
112
+ export const bridgeLogger = {
113
+ log(message: string, meta?: { sessionId?: string }) {
114
+ sessionLog(meta?.sessionId, message);
115
+ },
116
+ warn(message: string, meta?: { sessionId?: string }) {
117
+ sessionWarn(meta?.sessionId, message);
118
+ },
119
+ error(message: string, meta?: { sessionId?: string }) {
120
+ sessionError(meta?.sessionId, message);
121
+ },
122
+ getLogFilePath: () => logFile,
123
+ };
@@ -0,0 +1,179 @@
1
+ /**
2
+ * Cache-busting fix for prompt prefix cache eviction.
3
+ *
4
+ * PROBLEM: Every PromptInput we send to OpenCode (notifications, idle
5
+ * wakeups, ignored messages) creates a new user message. OpenCode's
6
+ * `createUserMessage` resolves variant relative to the chosen agent. If
7
+ * we don't pass model/variant, defaults take over and bust the provider
8
+ * prefix cache that the previous assistant turn warmed.
9
+ *
10
+ * APPROACH: Mirror what `opencode-xtra` does in production. Read recent
11
+ * messages from `client.session.messages()`, prefer the most recent
12
+ * assistant message, fall back to any role, MERGE across messages so
13
+ * partial fields (e.g. agent set on user message but not assistant) are
14
+ * filled in, and read BOTH the flat shape (`info.providerID`) used by
15
+ * AssistantMessage and the nested shape (`info.model.providerID`) used
16
+ * by UserMessage.
17
+ *
18
+ * IMPORTANT: This context is only meaningful for callers that DO trigger
19
+ * LLM inference (e.g. background-bash idle wakes with `noReply: false`).
20
+ * Callers using `noReply: true` (one-off ignored messages, warnings,
21
+ * announcements) never trigger inference, so they don't need model or
22
+ * variant — the model/variant pass-through there is unnecessary AND has
23
+ * been observed to crash OpenCode under some configurations. Limit
24
+ * model/variant pass-through to wake-style calls.
25
+ */
26
+
27
+ export interface ResolvedPromptContext {
28
+ agent?: string;
29
+ model?: { providerID: string; modelID: string };
30
+ variant?: string;
31
+ }
32
+
33
+ interface RawInfo {
34
+ role?: string;
35
+ agent?: string;
36
+ variant?: string;
37
+ providerID?: string;
38
+ modelID?: string;
39
+ model?: { providerID?: string; modelID?: string; variant?: string };
40
+ }
41
+
42
+ function isRecord(value: unknown): value is Record<string, unknown> {
43
+ return typeof value === "object" && value !== null;
44
+ }
45
+
46
+ function extractMessages(response: unknown): unknown[] {
47
+ if (Array.isArray(response)) return response;
48
+ if (isRecord(response) && Array.isArray(response.data)) return response.data;
49
+ return [];
50
+ }
51
+
52
+ function getRole(message: unknown): string | undefined {
53
+ if (!isRecord(message) || !isRecord(message.info)) return undefined;
54
+ return typeof message.info.role === "string" ? message.info.role : undefined;
55
+ }
56
+
57
+ function extractFromMessage(message: unknown): ResolvedPromptContext | null {
58
+ if (!isRecord(message) || !isRecord(message.info)) return null;
59
+ const info = message.info as RawInfo;
60
+ const modelInfo = isRecord(info.model) ? info.model : undefined;
61
+
62
+ const agent = typeof info.agent === "string" ? info.agent : undefined;
63
+ const providerID =
64
+ typeof modelInfo?.providerID === "string"
65
+ ? modelInfo.providerID
66
+ : typeof info.providerID === "string"
67
+ ? info.providerID
68
+ : undefined;
69
+ const modelID =
70
+ typeof modelInfo?.modelID === "string"
71
+ ? modelInfo.modelID
72
+ : typeof info.modelID === "string"
73
+ ? info.modelID
74
+ : undefined;
75
+ const variant =
76
+ typeof modelInfo?.variant === "string"
77
+ ? modelInfo.variant
78
+ : typeof info.variant === "string"
79
+ ? info.variant
80
+ : undefined;
81
+
82
+ if (!agent && (!providerID || !modelID) && !variant) return null;
83
+ const out: ResolvedPromptContext = {};
84
+ if (agent) out.agent = agent;
85
+ if (providerID && modelID) out.model = { providerID, modelID };
86
+ if (variant) out.variant = variant;
87
+ return out;
88
+ }
89
+
90
+ function mergeContexts(
91
+ base: ResolvedPromptContext,
92
+ patch: ResolvedPromptContext,
93
+ ): ResolvedPromptContext {
94
+ return {
95
+ agent: base.agent ?? patch.agent,
96
+ model: base.model ?? patch.model,
97
+ variant: base.variant ?? patch.variant,
98
+ };
99
+ }
100
+
101
+ function isComplete(ctx: ResolvedPromptContext): boolean {
102
+ return Boolean(ctx.agent && ctx.model && ctx.variant);
103
+ }
104
+
105
+ /**
106
+ * Read recent messages from the OpenCode session and resolve the most
107
+ * recent assistant prompt context. Falls back to user messages if no
108
+ * assistant has the field. Merges across messages so partial fields are
109
+ * filled in. Returns null if no usable context is found.
110
+ *
111
+ * Mirrors `resolveSessionPromptParams` in `opencode-xtra` (the working
112
+ * reference implementation).
113
+ */
114
+ export async function resolvePromptContext(
115
+ client: unknown,
116
+ sessionId: string,
117
+ ): Promise<ResolvedPromptContext | null> {
118
+ if (!client || !sessionId) return null;
119
+ const c = client as {
120
+ session?: {
121
+ messages?: (input: { path: { id: string } }) => Promise<{ data?: unknown[] } | unknown[]>;
122
+ };
123
+ };
124
+ if (typeof c.session?.messages !== "function") return null;
125
+
126
+ let messages: unknown[] = [];
127
+ try {
128
+ const response = await c.session.messages({ path: { id: sessionId } });
129
+ messages = extractMessages(response);
130
+ } catch {
131
+ return null;
132
+ }
133
+ if (messages.length === 0) return null;
134
+
135
+ // Pass 1: prefer the most recent assistant, merge older assistants to
136
+ // fill missing fields.
137
+ let result: ResolvedPromptContext = {};
138
+ for (let i = messages.length - 1; i >= 0; i -= 1) {
139
+ if (getRole(messages[i]) !== "assistant") continue;
140
+ const ctx = extractFromMessage(messages[i]);
141
+ if (!ctx) continue;
142
+ result = mergeContexts(result, ctx);
143
+ if (isComplete(result)) return result;
144
+ }
145
+
146
+ // Pass 2: fall back to any role (covers user messages, which carry
147
+ // model nested under `info.model`).
148
+ for (let i = messages.length - 1; i >= 0; i -= 1) {
149
+ const ctx = extractFromMessage(messages[i]);
150
+ if (!ctx) continue;
151
+ result = mergeContexts(result, ctx);
152
+ if (isComplete(result)) return result;
153
+ }
154
+
155
+ if (!result.agent && !result.model && !result.variant) return null;
156
+ return result;
157
+ }
158
+
159
+ // --- Compatibility shim for any older caller still using this name ---
160
+
161
+ export interface LastAssistantModel {
162
+ providerID: string;
163
+ modelID: string;
164
+ variant?: string;
165
+ }
166
+
167
+ /** @deprecated Use {@link resolvePromptContext} which also returns agent. */
168
+ export async function getLastAssistantModel(
169
+ client: unknown,
170
+ sessionId: string,
171
+ ): Promise<LastAssistantModel | null> {
172
+ const ctx = await resolvePromptContext(client, sessionId);
173
+ if (!ctx?.model) return null;
174
+ return {
175
+ providerID: ctx.model.providerID,
176
+ modelID: ctx.model.modelID,
177
+ ...(ctx.variant ? { variant: ctx.variant } : {}),
178
+ };
179
+ }