@cortexkit/aft-opencode 0.58.2 → 0.59.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/bash-wait-detach.d.ts.map +1 -1
- package/dist/bg-notifications.d.ts +8 -0
- package/dist/bg-notifications.d.ts.map +1 -1
- package/dist/bridge-bootstrap.d.ts +4 -4
- package/dist/config-live-reload.d.ts +36 -8
- package/dist/config-live-reload.d.ts.map +1 -1
- package/dist/config.d.ts +72 -13
- package/dist/config.d.ts.map +1 -1
- package/dist/configure-warnings.d.ts.map +1 -1
- package/dist/entry/server.js +3738 -1780
- package/dist/hooks/auto-update-checker/checker.d.ts.map +1 -1
- package/dist/hooks/auto-update-checker/index.d.ts.map +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3580 -1817
- package/dist/lsp-auto-install.d.ts.map +1 -1
- package/dist/lsp-github-install.d.ts +5 -2
- package/dist/lsp-github-install.d.ts.map +1 -1
- package/dist/notifications.d.ts +1 -1
- package/dist/notifications.d.ts.map +1 -1
- package/dist/rpc/contract.d.ts +1 -1
- package/dist/rpc/contract.d.ts.map +1 -1
- package/dist/rpc/register.d.ts +3 -1
- package/dist/rpc/register.d.ts.map +1 -1
- package/dist/shared/ignored-message.d.ts.map +1 -1
- package/dist/shared/read-vision.d.ts.map +1 -1
- package/dist/shared/rpc-client.d.ts +8 -0
- package/dist/shared/rpc-client.d.ts.map +1 -1
- package/dist/shared/rpc-server.d.ts.map +1 -1
- package/dist/shared/session-directory.d.ts +7 -5
- package/dist/shared/session-directory.d.ts.map +1 -1
- package/dist/shared/status.d.ts +1 -0
- package/dist/shared/status.d.ts.map +1 -1
- package/dist/shared/v2-context.d.ts +3 -0
- package/dist/shared/v2-context.d.ts.map +1 -0
- package/dist/subc-tool-schemas.d.ts +23 -1
- package/dist/subc-tool-schemas.d.ts.map +1 -1
- package/dist/tool-registration.d.ts +3 -3
- package/dist/tool-registration.d.ts.map +1 -1
- package/dist/tools/_shared.d.ts.map +1 -1
- package/dist/tools/bash.d.ts +17 -0
- package/dist/tools/bash.d.ts.map +1 -1
- package/dist/tools/bash_watch.d.ts +11 -1
- package/dist/tools/bash_watch.d.ts.map +1 -1
- package/dist/tools/definitions/v2.d.ts +2 -0
- package/dist/tools/definitions/v2.d.ts.map +1 -1
- package/dist/tools/hoisted.d.ts +18 -0
- package/dist/tools/hoisted.d.ts.map +1 -1
- package/dist/tools/inspect.d.ts +10 -1
- package/dist/tools/inspect.d.ts.map +1 -1
- package/dist/tools/permissions.d.ts +2 -1
- package/dist/tools/permissions.d.ts.map +1 -1
- package/dist/tools/search.d.ts.map +1 -1
- package/dist/tools/semantic.d.ts.map +1 -1
- package/dist/tui/aft-rpc.d.ts +14 -0
- package/dist/tui/aft-rpc.d.ts.map +1 -1
- package/dist/tui/v2-status.d.ts.map +1 -1
- package/dist/v2-host-tool-overlap.d.ts +66 -0
- package/dist/v2-host-tool-overlap.d.ts.map +1 -0
- package/dist/v2-prompt-detach.d.ts +1 -0
- package/dist/v2-prompt-detach.d.ts.map +1 -1
- package/dist/v2-tool-hooks.d.ts +6 -0
- package/dist/v2-tool-hooks.d.ts.map +1 -0
- package/dist/v2-workflow-hints.d.ts +4 -0
- package/dist/v2-workflow-hints.d.ts.map +1 -0
- package/dist/wakes/runtime-consumer.d.ts.map +1 -1
- package/dist/wakes/session-delivery.d.ts.map +1 -1
- package/package.json +9 -9
- package/src/entry/server-runtime.mjs +21 -1
- package/src/shared/ignored-message.ts +18 -0
- package/src/shared/read-vision.ts +14 -4
- package/src/shared/rpc-client.ts +46 -5
- package/src/shared/rpc-server.ts +10 -0
- package/src/shared/session-directory.ts +67 -19
- package/src/shared/status.ts +15 -5
- package/src/shared/v2-context.ts +7 -0
- package/src/tui/aft-rpc.ts +11 -0
- package/src/tui/index.tsx +20 -16
- package/src/tui/sidebar-view.tsx +9 -1
- package/src/tui/v2-status.ts +7 -5
- package/src/tui/v2.tsx +47 -0
- package/src/tui-compiled/aft-rpc.ts +11 -0
- package/src/tui-compiled/index.tsx +27 -16
- package/src/tui-compiled/sidebar-view.tsx +11 -2
- package/src/tui-compiled/v2-status.ts +7 -5
- package/src/tui-compiled/v2.tsx +26 -0
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* On OpenCode 2, AFT's `apply_patch` and `bash` sit beside the host's own
|
|
3
|
+
* `patch` and `shell` tools unless the user's opencode config turns the host
|
|
4
|
+
* plugins off (`-opencode.tool.patch` / `-opencode.tool.shell` under
|
|
5
|
+
* `plugins`, which `aft setup` and `aft doctor --fix` write). Someone who
|
|
6
|
+
* installed AFT without setup, or turned the host tools back on, gets two
|
|
7
|
+
* editing tools and two shells with nothing saying why. This module tells the
|
|
8
|
+
* user once per host process and points at `doctor --fix`.
|
|
9
|
+
*
|
|
10
|
+
* What counts is what the host actually registered: `context.tool.list()`
|
|
11
|
+
* returns the host's tools after every plugin's transform, keyed by the name
|
|
12
|
+
* the model sees. The check waits for the first user prompt rather than
|
|
13
|
+
* running at plugin start, because at start other plugins (the host's own
|
|
14
|
+
* patch and shell plugins included) may not have registered yet, and because
|
|
15
|
+
* the notice is an RPC event the host does not replay: a TUI that has not
|
|
16
|
+
* subscribed yet would miss it, while a prompt typed into the TUI proves its
|
|
17
|
+
* subscription is live. A host without `tool.list()`
|
|
18
|
+
* (OpenCode 2.0.3 has none) or a list call that fails gets no notice: the
|
|
19
|
+
* reason is logged once at debug level and nothing is guessed.
|
|
20
|
+
*
|
|
21
|
+
* The notice must reach the user and never the model. Nothing is written into
|
|
22
|
+
* the session: OpenCode 2 sends a synthetic session record to the model as a
|
|
23
|
+
* user message on every later turn, where a notice would read as an
|
|
24
|
+
* instruction. A server plugin has no toast or dialog of its own, so the
|
|
25
|
+
* notice goes out as an AFT RPC event and AFT's OpenCode 2 TUI plugin shows
|
|
26
|
+
* it as a toast. The desktop and web apps run no TUI plugin and show nothing;
|
|
27
|
+
* `aft doctor` reports the same problem there.
|
|
28
|
+
*/
|
|
29
|
+
import { Effect } from "effect";
|
|
30
|
+
/** An AFT tool and the OpenCode 2 built-in that does the same job. */
|
|
31
|
+
export interface HostToolOverlap {
|
|
32
|
+
readonly aft: "apply_patch" | "bash";
|
|
33
|
+
readonly host: "patch" | "shell";
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* The pairs where AFT registered its tool (it is not in `disabled_tools`) and
|
|
37
|
+
* the host also has its built-in, in a fixed order. `hostToolNames` is the
|
|
38
|
+
* host's whole registry after every transform, AFT's own tools included, so a
|
|
39
|
+
* name AFT registered itself is never taken for the host's built-in.
|
|
40
|
+
*/
|
|
41
|
+
export declare function findHostToolOverlaps(hostToolNames: ReadonlySet<string>, registeredAftTools: ReadonlySet<string>): HostToolOverlap[];
|
|
42
|
+
/** One notice for every overlapping pair, or null when there is none. */
|
|
43
|
+
export declare function hostToolOverlapNotice(overlaps: readonly HostToolOverlap[]): string | null;
|
|
44
|
+
/** Reset the once-per-process state. Tests only. */
|
|
45
|
+
export declare function __resetHostToolOverlapNoticeForTests(): void;
|
|
46
|
+
interface PromptEvent {
|
|
47
|
+
readonly sessionID: string;
|
|
48
|
+
readonly metadata?: Record<string, unknown>;
|
|
49
|
+
}
|
|
50
|
+
/** Hands the notice text to the user-only channel (the TUI toast event). */
|
|
51
|
+
export type HostToolOverlapDelivery = (message: string) => Promise<void>;
|
|
52
|
+
/**
|
|
53
|
+
* The session `prompt` hook for one Location. It never fails and never waits:
|
|
54
|
+
* the host holds the prompt until the hook returns, so the check runs in the
|
|
55
|
+
* background after it.
|
|
56
|
+
*/
|
|
57
|
+
export declare function createHostToolOverlapPromptHook(context: unknown, registeredAftTools: ReadonlySet<string>, deliver: HostToolOverlapDelivery, onChecked?: (done: Promise<void>) => void): (event: PromptEvent) => Effect.Effect<void>;
|
|
58
|
+
/**
|
|
59
|
+
* Register the overlap check on an OpenCode 2 host's session `prompt` hook.
|
|
60
|
+
* `registeredAftTools` is AFT's final tool surface, so a tool listed in
|
|
61
|
+
* `disabled_tools` never counts as an overlap. `deliver` must reach the user
|
|
62
|
+
* only, never the session.
|
|
63
|
+
*/
|
|
64
|
+
export declare function registerV2HostToolOverlapNotice(context: unknown, registeredAftTools: ReadonlySet<string>, deliver: HostToolOverlapDelivery, onChecked?: (done: Promise<void>) => void): Effect.Effect<boolean, never, unknown>;
|
|
65
|
+
export {};
|
|
66
|
+
//# sourceMappingURL=v2-host-tool-overlap.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"v2-host-tool-overlap.d.ts","sourceRoot":"","sources":["../src/v2-host-tool-overlap.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AACH,OAAO,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AAKhC,sEAAsE;AACtE,MAAM,WAAW,eAAe;IAC9B,QAAQ,CAAC,GAAG,EAAE,aAAa,GAAG,MAAM,CAAC;IACrC,QAAQ,CAAC,IAAI,EAAE,OAAO,GAAG,OAAO,CAAC;CAClC;AASD;;;;;GAKG;AACH,wBAAgB,oBAAoB,CAClC,aAAa,EAAE,WAAW,CAAC,MAAM,CAAC,EAClC,kBAAkB,EAAE,WAAW,CAAC,MAAM,CAAC,GACtC,eAAe,EAAE,CAOnB;AAED,yEAAyE;AACzE,wBAAgB,qBAAqB,CAAC,QAAQ,EAAE,SAAS,eAAe,EAAE,GAAG,MAAM,GAAG,IAAI,CAazF;AAQD,oDAAoD;AACpD,wBAAgB,oCAAoC,IAAI,IAAI,CAG3D;AAwDD,UAAU,WAAW;IACnB,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CAC7C;AAED,4EAA4E;AAC5E,MAAM,MAAM,uBAAuB,GAAG,CAAC,OAAO,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;AAgCzE;;;;GAIG;AACH,wBAAgB,+BAA+B,CAC7C,OAAO,EAAE,OAAO,EAChB,kBAAkB,EAAE,WAAW,CAAC,MAAM,CAAC,EACvC,OAAO,EAAE,uBAAuB,EAChC,SAAS,CAAC,EAAE,CAAC,IAAI,EAAE,OAAO,CAAC,IAAI,CAAC,KAAK,IAAI,GACxC,CAAC,KAAK,EAAE,WAAW,KAAK,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,CAe7C;AAOD;;;;;GAKG;AACH,wBAAgB,+BAA+B,CAC7C,OAAO,EAAE,OAAO,EAChB,kBAAkB,EAAE,WAAW,CAAC,MAAM,CAAC,EACvC,OAAO,EAAE,uBAAuB,EAChC,SAAS,CAAC,EAAE,CAAC,IAAI,EAAE,OAAO,CAAC,IAAI,CAAC,KAAK,IAAI,GACxC,MAAM,CAAC,MAAM,CAAC,OAAO,EAAE,KAAK,EAAE,OAAO,CAAC,CAkBxC"}
|
|
@@ -35,6 +35,7 @@ export interface V2PromptDetachRuntime {
|
|
|
35
35
|
* `bash.detach_on_user_message` applies to the next message.
|
|
36
36
|
*/
|
|
37
37
|
getConfig(): AftConfig;
|
|
38
|
+
registerSession?: (sessionID: string) => void;
|
|
38
39
|
}
|
|
39
40
|
/**
|
|
40
41
|
* Run the shared chat-message decision on one OpenCode 2 prompt and write the
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"v2-prompt-detach.d.ts","sourceRoot":"","sources":["../src/v2-prompt-detach.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"v2-prompt-detach.d.ts","sourceRoot":"","sources":["../src/v2-prompt-detach.ts"],"names":[],"mappings":"AAUA,OAAO,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AAEhC,OAAO,EAAE,gCAAgC,EAAE,MAAM,uBAAuB,CAAC;AACzE,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AAI7C,8DAA8D;AAC9D,UAAU,aAAa;IACrB,KAAK,EAAE,MAAM,CAAC;IACd,GAAG,EAAE,MAAM,CAAC;IACZ,IAAI,EAAE,MAAM,CAAC;CACd;AAED,UAAU,cAAc;IACtB,OAAO,CAAC,EAAE,aAAa,CAAC;CACzB;AAED,0FAA0F;AAC1F,MAAM,WAAW,iBAAiB;IAChC,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,MAAM,EAAE;QACN,IAAI,EAAE,MAAM,CAAC;QACb,KAAK,CAAC,EAAE,cAAc,EAAE,CAAC;QACzB,MAAM,CAAC,EAAE,cAAc,EAAE,CAAC;QAC1B,MAAM,CAAC,EAAE,cAAc,EAAE,CAAC;KAC3B,CAAC;IACF,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CACpC;AAED,KAAK,eAAe,GAAG,UAAU,CAAC,OAAO,gCAAgC,CAAC,CAAC,CAAC,CAAC,CAAC;AAE9E,MAAM,WAAW,qBAAqB;IACpC,gEAAgE;IAChE,QAAQ,CAAC,IAAI,EAAE,eAAe,CAAC;IAC/B;;;OAGG;IACH,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B;;;OAGG;IACH,SAAS,IAAI,SAAS,CAAC;IACvB,eAAe,CAAC,EAAE,CAAC,SAAS,EAAE,MAAM,KAAK,IAAI,CAAC;CAC/C;AAqCD;;;;;;;;;;;;GAYG;AACH,wBAAgB,6BAA6B,CAC3C,OAAO,EAAE,qBAAqB,EAC9B,KAAK,EAAE,iBAAiB,GACvB,OAAO,CAuBT;AAMD;;;;;;GAMG;AACH,wBAAgB,wBAAwB,CACtC,OAAO,EAAE,qBAAqB,GAC7B,CAAC,KAAK,EAAE,iBAAiB,KAAK,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,CAgBnD;AAOD;;;;;GAKG;AACH,wBAAgB,0BAA0B,CACxC,OAAO,EAAE,OAAO,EAChB,OAAO,EAAE,qBAAqB,GAC7B,MAAM,CAAC,MAAM,CAAC,OAAO,EAAE,KAAK,EAAE,OAAO,CAAC,CAYxC"}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import { Effect } from "effect";
|
|
2
|
+
/** Port the V1 raw-argument, result-text and current-model seams to V2. */
|
|
3
|
+
export declare function registerV2ToolHooks(context: unknown, runtime: {
|
|
4
|
+
hashlineEffective?: boolean;
|
|
5
|
+
}, registeredTools: ReadonlySet<string>): Effect.Effect<void, never, import("effect/Scope").Scope>;
|
|
6
|
+
//# sourceMappingURL=v2-tool-hooks.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"v2-tool-hooks.d.ts","sourceRoot":"","sources":["../src/v2-tool-hooks.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,MAAM,EAAU,MAAM,QAAQ,CAAC;AAqBxC,2EAA2E;AAC3E,wBAAgB,mBAAmB,CACjC,OAAO,EAAE,OAAO,EAChB,OAAO,EAAE;IAAE,iBAAiB,CAAC,EAAE,OAAO,CAAA;CAAE,EACxC,eAAe,EAAE,WAAW,CAAC,MAAM,CAAC,4DA6DrC"}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import { Effect } from "effect";
|
|
2
|
+
/** V2 consumes session system hooks, not V1's chat.system.transform. */
|
|
3
|
+
export declare function registerV2WorkflowHints(context: unknown, hintsBlock: string | null): Effect.Effect<boolean, never, import("effect/Scope").Scope>;
|
|
4
|
+
//# sourceMappingURL=v2-workflow-hints.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"v2-workflow-hints.d.ts","sourceRoot":"","sources":["../src/v2-workflow-hints.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AAIhC,wEAAwE;AACxE,wBAAgB,uBAAuB,CAAC,OAAO,EAAE,OAAO,EAAE,UAAU,EAAE,MAAM,GAAG,IAAI,+DAelF"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"runtime-consumer.d.ts","sourceRoot":"","sources":["../../src/wakes/runtime-consumer.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAIV,aAAa,EACd,MAAM,uBAAuB,CAAC;AAM/B,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,4BAA4B,CAAC;AAClE,OAAO,EAAE,KAAK,kBAAkB,EAAqB,MAAM,uBAAuB,CAAC;AAEnF,MAAM,WAAW,iBAAiB;IAChC,QAAQ,CAAC,OAAO,EAAE,kBAAkB,CAAC;CACtC;
|
|
1
|
+
{"version":3,"file":"runtime-consumer.d.ts","sourceRoot":"","sources":["../../src/wakes/runtime-consumer.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAIV,aAAa,EACd,MAAM,uBAAuB,CAAC;AAM/B,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,4BAA4B,CAAC;AAClE,OAAO,EAAE,KAAK,kBAAkB,EAAqB,MAAM,uBAAuB,CAAC;AAEnF,MAAM,WAAW,iBAAiB;IAChC,QAAQ,CAAC,OAAO,EAAE,kBAAkB,CAAC;CACtC;AAyJD,mFAAmF;AACnF,wBAAgB,2BAA2B,IAAI,IAAI,CAKlD;AAED,QAAA,MAAM,mBAAmB,EAAE,IAAI,CAAC,aAAa,EAAE,kBAAkB,GAAG,mBAAmB,CAGtF,CAAC;AAEF,MAAM,WAAW,iBAAkB,SAAQ,eAAe;IACxD,QAAQ,CAAC,aAAa,EAAE,OAAO,mBAAmB,CAAC;IACnD,OAAO,IAAI,IAAI,CAAC;CACjB;AAED;;;;GAIG;AACH,wBAAgB,uBAAuB,CAAC,OAAO,EAAE,iBAAiB,GAAG,iBAAiB,CAiErF"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"session-delivery.d.ts","sourceRoot":"","sources":["../../src/wakes/session-delivery.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,
|
|
1
|
+
{"version":3,"file":"session-delivery.d.ts","sourceRoot":"","sources":["../../src/wakes/session-delivery.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAQ,MAAM,QAAQ,CAAC;AAEtC;;;;;GAKG;AACH,eAAO,MAAM,iBAAiB,QAAQ,CAAC;AAEvC,yEAAyE;AACzE,wBAAgB,qBAAqB,CAAC,QAAQ,EAAE,OAAO,GAAG,OAAO,CAMhE;AAED,MAAM,WAAW,oBAAoB;IACnC,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,QAAQ,EAAE,OAAO,CAAC;IAC3B,QAAQ,CAAC,QAAQ,CAAC,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC;IACtD,QAAQ,CAAC,MAAM,CAAC,EAAE,OAAO,CAAC;CAC3B;AAED,MAAM,WAAW,uBAAuB;IACtC,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,CAAC;IAC9B,QAAQ,CAAC,QAAQ,CAAC,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC;IACtD,QAAQ,CAAC,MAAM,EAAE,KAAK,CAAC;CACxB;AAED,MAAM,WAAW,kBAAkB;IACjC,MAAM,CAAC,KAAK,EAAE,oBAAoB,GAAG,MAAM,CAAC,MAAM,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC;IACrE,SAAS,CAAC,KAAK,EAAE,uBAAuB,GAAG,MAAM,CAAC,MAAM,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC;CAC5E;AAED,MAAM,WAAW,cAAc;IAC7B,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,OAAO,EAAE,SAAS,MAAM,EAAE,CAAC;IACpC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,QAAQ,CAAC,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC;CACvD;AAED,MAAM,WAAW,gBAAgB;IAC/B,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,CAAC;IAC9B,QAAQ,CAAC,QAAQ,CAAC,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC;CACvD;AAED;;;GAGG;AACH,qBAAa,iBAAiB;IAIhB,OAAO,CAAC,QAAQ,CAAC,OAAO;IAHpC,OAAO,CAAC,QAAQ,CAAC,mBAAmB,CAAqB;IACzD,OAAO,CAAC,QAAQ,CAAS;gBAEI,OAAO,EAAE,kBAAkB;IAExD,UAAU,CAAC,KAAK,EAAE,cAAc,GAAG,MAAM,CAAC,MAAM,CAAC,OAAO,EAAE,OAAO,CAAC;IAqClE,MAAM,CAAC,KAAK,EAAE,gBAAgB,GAAG,MAAM,CAAC,MAAM,CAAC,IAAI,EAAE,OAAO,CAAC;IAe7D,OAAO,IAAI,IAAI;CAKhB"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cortexkit/aft-opencode",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.59.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",
|
|
@@ -33,13 +33,13 @@
|
|
|
33
33
|
"schema": "bun scripts/build-schema.ts",
|
|
34
34
|
"build:tool-schemas": "bun run scripts/build-tool-schemas.ts",
|
|
35
35
|
"prepublishOnly": "bun run build",
|
|
36
|
-
"test:unit": "bun test src/__tests__ --path-ignore-patterns 'src/__tests__/e2e/**'",
|
|
36
|
+
"test:unit": "bun test src/__tests__ ./test/ ./__tests__/ --path-ignore-patterns 'src/__tests__/e2e/**'",
|
|
37
37
|
"build:tui": "bun scripts/build-tui.ts",
|
|
38
38
|
"smoke:tui": "bun test ./scripts/smoke-tui-pack-install.ts"
|
|
39
39
|
},
|
|
40
40
|
"dependencies": {
|
|
41
41
|
"@clack/prompts": "^1.6.0",
|
|
42
|
-
"@cortexkit/aft-bridge": "0.
|
|
42
|
+
"@cortexkit/aft-bridge": "0.59.0",
|
|
43
43
|
"@opencode/client": "2.0.11",
|
|
44
44
|
"@opentui/core": "0.4.3",
|
|
45
45
|
"@opentui/solid": "0.4.3",
|
|
@@ -50,12 +50,12 @@
|
|
|
50
50
|
"zod": "^4.4.3"
|
|
51
51
|
},
|
|
52
52
|
"optionalDependencies": {
|
|
53
|
-
"@cortexkit/aft-darwin-arm64": "0.
|
|
54
|
-
"@cortexkit/aft-darwin-x64": "0.
|
|
55
|
-
"@cortexkit/aft-linux-arm64": "0.
|
|
56
|
-
"@cortexkit/aft-linux-x64": "0.
|
|
57
|
-
"@cortexkit/aft-win32-arm64": "0.
|
|
58
|
-
"@cortexkit/aft-win32-x64": "0.
|
|
53
|
+
"@cortexkit/aft-darwin-arm64": "0.59.0",
|
|
54
|
+
"@cortexkit/aft-darwin-x64": "0.59.0",
|
|
55
|
+
"@cortexkit/aft-linux-arm64": "0.59.0",
|
|
56
|
+
"@cortexkit/aft-linux-x64": "0.59.0",
|
|
57
|
+
"@cortexkit/aft-win32-arm64": "0.59.0",
|
|
58
|
+
"@cortexkit/aft-win32-x64": "0.59.0"
|
|
59
59
|
},
|
|
60
60
|
"devDependencies": {
|
|
61
61
|
"@opencode-ai/plugin": "^1.17.11",
|
|
@@ -18,8 +18,12 @@ import { debug, log, warn } from "../logger.js";
|
|
|
18
18
|
import { resolvePluginVersion } from "../plugin-version.js";
|
|
19
19
|
import { registerAftConfigErrorRpc, registerAftRpc } from "../rpc/register.js";
|
|
20
20
|
import { hoistedV2ToolConsumers, v2PromptChannelFor } from "../tools/hoisted/v2.js";
|
|
21
|
+
import { registerV2HostToolOverlapNotice } from "../v2-host-tool-overlap.js";
|
|
21
22
|
import { registerV2PromptDetachHook } from "../v2-prompt-detach.js";
|
|
23
|
+
import { registerV2ToolHooks } from "../v2-tool-hooks.js";
|
|
24
|
+
import { registerV2WorkflowHints } from "../v2-workflow-hints.js";
|
|
22
25
|
import { createV2RuntimeConsumer } from "../wakes/runtime-consumer.js";
|
|
26
|
+
import { buildHintsForRegisteredTools } from "../workflow-hints.js";
|
|
23
27
|
import {
|
|
24
28
|
buildAftToolDefinitions,
|
|
25
29
|
openCodeHashlineEffective,
|
|
@@ -36,6 +40,9 @@ const defaults = {
|
|
|
36
40
|
registerRpc: registerAftRpc,
|
|
37
41
|
registerConfigErrorRpc: registerAftConfigErrorRpc,
|
|
38
42
|
registerPromptHook: registerV2PromptDetachHook,
|
|
43
|
+
registerToolHooks: registerV2ToolHooks,
|
|
44
|
+
registerWorkflowHints: registerV2WorkflowHints,
|
|
45
|
+
registerHostToolOverlapNotice: registerV2HostToolOverlapNotice,
|
|
39
46
|
// The prompt server comes from the user-only `opencode` block of the
|
|
40
47
|
// Location's config; without it AFT finds the server it runs inside.
|
|
41
48
|
toolConsumers: (context, config) => ({
|
|
@@ -119,6 +126,7 @@ async function bootLocation(context, location, dependencies) {
|
|
|
119
126
|
const { hashlineEditRegistered } = applyToolSurfaceOverrides(pool, config, registeredTools);
|
|
120
127
|
reportHashlineDowngrade(config, registeredTools, notify);
|
|
121
128
|
toolContext.hashlineEffective = hashlineEditRegistered;
|
|
129
|
+
const hintsBlock = buildHintsForRegisteredTools(config, registeredTools, hashlineEditRegistered);
|
|
122
130
|
// Keep this Location's live config keys current when a config file changes.
|
|
123
131
|
// The finalizer that releases the bridge also stops the watch.
|
|
124
132
|
const liveConfigReload = dependencies.startLiveConfigReload({
|
|
@@ -131,7 +139,7 @@ async function bootLocation(context, location, dependencies) {
|
|
|
131
139
|
},
|
|
132
140
|
notify,
|
|
133
141
|
});
|
|
134
|
-
return { consumers, pool, tools, liveConfigReload, toolContext, directory };
|
|
142
|
+
return { consumers, pool, tools, liveConfigReload, toolContext, directory, hintsBlock };
|
|
135
143
|
}
|
|
136
144
|
|
|
137
145
|
/**
|
|
@@ -221,6 +229,7 @@ export function makeServerEffect(overrides = {}) {
|
|
|
221
229
|
pool: runtime.pool,
|
|
222
230
|
projectRoot: runtime.directory,
|
|
223
231
|
getConfig: () => runtime.toolContext.config,
|
|
232
|
+
registerSession: runtime.consumers.registerSession,
|
|
224
233
|
});
|
|
225
234
|
yield* dependencies.registerTools(
|
|
226
235
|
context,
|
|
@@ -228,6 +237,17 @@ export function makeServerEffect(overrides = {}) {
|
|
|
228
237
|
runtime.tools,
|
|
229
238
|
runtime.consumers,
|
|
230
239
|
);
|
|
240
|
+
yield* dependencies.registerToolHooks(context, runtime.toolContext, new Set(Object.keys(runtime.tools)));
|
|
241
|
+
yield* dependencies.registerWorkflowHints(context, runtime.hintsBlock);
|
|
242
|
+
// Tell the user once when the host's own patch or shell tool still runs
|
|
243
|
+
// beside AFT's apply_patch or bash. Checked on the first prompt, once
|
|
244
|
+
// every plugin has registered its tools, and shown by AFT's TUI plugin
|
|
245
|
+
// as a toast; nothing is written into the session, which the model reads.
|
|
246
|
+
yield* dependencies.registerHostToolOverlapNotice(
|
|
247
|
+
context,
|
|
248
|
+
new Set(Object.keys(runtime.tools)),
|
|
249
|
+
(message) => rpc.emitHostToolOverlap({ message }),
|
|
250
|
+
);
|
|
231
251
|
});
|
|
232
252
|
};
|
|
233
253
|
}
|
|
@@ -1,4 +1,7 @@
|
|
|
1
|
+
import { Effect } from "effect";
|
|
2
|
+
|
|
1
3
|
import { resolvePromptContext } from "./last-assistant-model.js";
|
|
4
|
+
import { isV2PluginContext } from "./v2-context.js";
|
|
2
5
|
|
|
3
6
|
/**
|
|
4
7
|
* Append an `ignored: true` synthetic user message to a session.
|
|
@@ -24,6 +27,21 @@ export async function sendIgnoredMessage(
|
|
|
24
27
|
sessionID: string,
|
|
25
28
|
text: string,
|
|
26
29
|
): Promise<void> {
|
|
30
|
+
if (isV2PluginContext(client)) {
|
|
31
|
+
const context = client as {
|
|
32
|
+
session: {
|
|
33
|
+
synthetic(input: {
|
|
34
|
+
sessionID: string;
|
|
35
|
+
text: string;
|
|
36
|
+
resume: false;
|
|
37
|
+
}): Effect.Effect<unknown, unknown>;
|
|
38
|
+
};
|
|
39
|
+
};
|
|
40
|
+
// V2 owns the current agent/model itself. Synthetic inbox entries neither
|
|
41
|
+
// switch that context nor resume a turn, and must actually run as Effects.
|
|
42
|
+
await Effect.runPromise(context.session.synthetic({ sessionID, text, resume: false }));
|
|
43
|
+
return;
|
|
44
|
+
}
|
|
27
45
|
const typedClient = client as {
|
|
28
46
|
session?: {
|
|
29
47
|
prompt?: (input: unknown) => unknown;
|
|
@@ -1,4 +1,7 @@
|
|
|
1
|
+
import { Effect } from "effect";
|
|
2
|
+
|
|
1
3
|
import { resolvePromptContext } from "./last-assistant-model.js";
|
|
4
|
+
import { isV2PluginContext } from "./v2-context.js";
|
|
2
5
|
|
|
3
6
|
export const VISION_HOST_TIMEOUT_MS = 1500;
|
|
4
7
|
type Model = { providerID: string; modelID: string };
|
|
@@ -69,12 +72,19 @@ export async function currentSessionVisionCapability(
|
|
|
69
72
|
let capability = state.capabilities.get(key);
|
|
70
73
|
if (!capability) {
|
|
71
74
|
capability = (async () => {
|
|
72
|
-
const api = (client as { provider?: { list?: () =>
|
|
75
|
+
const api = (client as { provider?: { list?: () => unknown } }).provider;
|
|
73
76
|
if (!api?.list) return undefined;
|
|
74
|
-
const list =
|
|
77
|
+
const list = async () => {
|
|
78
|
+
const result = api.list?.();
|
|
79
|
+
return isV2PluginContext(client)
|
|
80
|
+
? Effect.runPromise(result as Effect.Effect<unknown, unknown>)
|
|
81
|
+
: await result;
|
|
82
|
+
};
|
|
75
83
|
const listed = record(await bounded(list));
|
|
76
84
|
const catalog = record(listed?.data) ?? listed;
|
|
77
|
-
const providers =
|
|
85
|
+
const providers = Array.isArray(listed?.data)
|
|
86
|
+
? listed.data
|
|
87
|
+
: (catalog?.all ?? catalog?.providers);
|
|
78
88
|
if (!Array.isArray(providers)) return undefined;
|
|
79
89
|
const provider = providers.map(record).find((entry) => entry?.id === currentModel.providerID);
|
|
80
90
|
const models = provider?.models;
|
|
@@ -83,7 +93,7 @@ export async function currentSessionVisionCapability(
|
|
|
83
93
|
? models.find((entry) => record(entry)?.id === currentModel.modelID)
|
|
84
94
|
: record(models)?.[currentModel.modelID],
|
|
85
95
|
);
|
|
86
|
-
const inputs = record(entry?.modalities)?.input;
|
|
96
|
+
const inputs = record(entry?.capabilities)?.input ?? record(entry?.modalities)?.input;
|
|
87
97
|
return Array.isArray(inputs)
|
|
88
98
|
? inputs.includes("image")
|
|
89
99
|
: typeof entry?.attachment === "boolean"
|
package/src/shared/rpc-client.ts
CHANGED
|
@@ -133,19 +133,46 @@ export class AftRpcClient {
|
|
|
133
133
|
const { signal } = options;
|
|
134
134
|
throwIfAborted(signal);
|
|
135
135
|
|
|
136
|
+
// Fast path: reuse the endpoint that served the last warm response while
|
|
137
|
+
// it is still the newest live port file, skipping discovery's sequential
|
|
138
|
+
// health checks. Anything other than an accepted warm response falls back
|
|
139
|
+
// to the full scan below.
|
|
140
|
+
let placeholder: T | null = null;
|
|
141
|
+
let lastError: unknown = null;
|
|
142
|
+
let answeredPort: number | null = null;
|
|
143
|
+
const cached = this.cachedEndpoint();
|
|
144
|
+
if (cached) {
|
|
145
|
+
try {
|
|
146
|
+
const result = await this.callOne<T>(method, params, cached, signal);
|
|
147
|
+
if (this.looksLikePlaceholder(result)) {
|
|
148
|
+
placeholder = result;
|
|
149
|
+
answeredPort = cached.port;
|
|
150
|
+
} else if (!options.accept || options.accept(result)) {
|
|
151
|
+
return result;
|
|
152
|
+
} else {
|
|
153
|
+
answeredPort = cached.port;
|
|
154
|
+
}
|
|
155
|
+
} catch {
|
|
156
|
+
throwIfAborted(signal);
|
|
157
|
+
}
|
|
158
|
+
this.port = null;
|
|
159
|
+
this.token = null;
|
|
160
|
+
}
|
|
161
|
+
|
|
136
162
|
// Try ALL discovered ports for this project (OpenCode TUI under --port 0
|
|
137
163
|
// loads our plugin twice in the same process, so two RPC servers listen
|
|
138
|
-
// and we have to try both — only one's bridge is actually warm).
|
|
139
|
-
|
|
140
|
-
|
|
164
|
+
// and we have to try both — only one's bridge is actually warm). A port
|
|
165
|
+
// that already answered on the fast path is not asked again.
|
|
166
|
+
const infos = (await this.resolvePortInfos(signal)).filter(
|
|
167
|
+
(info) => info.port !== answeredPort,
|
|
168
|
+
);
|
|
169
|
+
if (infos.length === 0 && placeholder === null) {
|
|
141
170
|
throw new Error("AFT RPC server not available");
|
|
142
171
|
}
|
|
143
172
|
|
|
144
173
|
// First pass: try every port. Prefer responses that look like "warm
|
|
145
174
|
// bridge" output (i.e. not the synthetic `status: "not_initialized"`
|
|
146
175
|
// placeholder served when this instance's bridge hasn't been spawned).
|
|
147
|
-
let placeholder: T | null = null;
|
|
148
|
-
let lastError: unknown = null;
|
|
149
176
|
for (const info of infos) {
|
|
150
177
|
throwIfAborted(signal);
|
|
151
178
|
try {
|
|
@@ -250,6 +277,20 @@ export class AftRpcClient {
|
|
|
250
277
|
return status === "not_initialized";
|
|
251
278
|
}
|
|
252
279
|
|
|
280
|
+
/**
|
|
281
|
+
* The endpoint that served the last warm response, if it is still the newest
|
|
282
|
+
* live port file for this project. Discovery tries ports newest first and
|
|
283
|
+
* returns the first warm answer, so while this holds the full scan would
|
|
284
|
+
* reach the same server first. Reading the port files also drops entries
|
|
285
|
+
* whose owning process has died, so a dead server is never reused.
|
|
286
|
+
*/
|
|
287
|
+
private cachedEndpoint(): PortInfo | null {
|
|
288
|
+
if (this.port === null) return null;
|
|
289
|
+
const [newest] = this.readAllPortFiles();
|
|
290
|
+
if (!newest || newest.port !== this.port || newest.token !== this.token) return null;
|
|
291
|
+
return newest;
|
|
292
|
+
}
|
|
293
|
+
|
|
253
294
|
/** Check if any RPC server is reachable. */
|
|
254
295
|
async isAvailable(): Promise<boolean> {
|
|
255
296
|
try {
|
package/src/shared/rpc-server.ts
CHANGED
|
@@ -319,6 +319,10 @@ export class AftRpcServer {
|
|
|
319
319
|
return json({ error: "Invalid JSON" }, 400);
|
|
320
320
|
}
|
|
321
321
|
|
|
322
|
+
if (!params || typeof params !== "object" || Array.isArray(params)) {
|
|
323
|
+
return json({ error: "Expected a JSON object" }, 400);
|
|
324
|
+
}
|
|
325
|
+
|
|
322
326
|
if (!tokensMatch(typeof params.token === "string" ? params.token : "", this.token)) {
|
|
323
327
|
return json({ error: "Forbidden" }, 403);
|
|
324
328
|
}
|
|
@@ -453,6 +457,12 @@ export class AftRpcServer {
|
|
|
453
457
|
return;
|
|
454
458
|
}
|
|
455
459
|
|
|
460
|
+
if (!params || typeof params !== "object" || Array.isArray(params)) {
|
|
461
|
+
res.writeHead(400, { "Content-Type": "application/json" });
|
|
462
|
+
res.end(JSON.stringify({ error: "Expected a JSON object" }));
|
|
463
|
+
return;
|
|
464
|
+
}
|
|
465
|
+
|
|
456
466
|
if (!tokensMatch(typeof params.token === "string" ? params.token : "", this.token)) {
|
|
457
467
|
res.writeHead(403, { "Content-Type": "application/json" });
|
|
458
468
|
res.end(JSON.stringify({ error: "Forbidden" }));
|
|
@@ -68,12 +68,14 @@ function isV2PluginContext(client: unknown): client is V2PluginContextShape {
|
|
|
68
68
|
}
|
|
69
69
|
|
|
70
70
|
/**
|
|
71
|
-
* How long
|
|
72
|
-
*
|
|
73
|
-
*
|
|
74
|
-
* Location's directory instead.
|
|
71
|
+
* How long a session lookup may run on either host version. Tool routing
|
|
72
|
+
* awaits this lookup, so an unresponsive host must not hold the call forever.
|
|
73
|
+
* After this budget the call proceeds with its own directory instead.
|
|
75
74
|
*/
|
|
76
|
-
export const
|
|
75
|
+
export const LOOKUP_TIMEOUT_MS = 1_000;
|
|
76
|
+
export const V2_SESSION_LOOKUP_TIMEOUT_MS = LOOKUP_TIMEOUT_MS;
|
|
77
|
+
/** A failed lookup is provisional; retry later without querying on every call. */
|
|
78
|
+
export const FAILED_LOOKUP_RETRY_MS = 30_000;
|
|
77
79
|
|
|
78
80
|
interface OpenCodeClientShape {
|
|
79
81
|
session?: {
|
|
@@ -86,14 +88,15 @@ interface OpenCodeClientShape {
|
|
|
86
88
|
}
|
|
87
89
|
|
|
88
90
|
interface CacheEntry {
|
|
89
|
-
/** Resolved directory, or `null`
|
|
91
|
+
/** Resolved directory, or `null` while a failed lookup is cooling down. */
|
|
90
92
|
directory: string | null;
|
|
91
|
-
/**
|
|
93
|
+
/** When the answer was recorded; failed lookups become retryable after 30 seconds. */
|
|
92
94
|
recordedAt: number;
|
|
93
95
|
}
|
|
94
96
|
|
|
95
97
|
const CACHE_MAX_ENTRIES = 200;
|
|
96
98
|
const cache = new Map<string, CacheEntry>();
|
|
99
|
+
const inflight = new Map<string, Promise<string | null>>();
|
|
97
100
|
|
|
98
101
|
/**
|
|
99
102
|
* Resolve the project directory the session was created with from the SDK's
|
|
@@ -114,19 +117,63 @@ export async function getSessionDirectory(
|
|
|
114
117
|
if (!sessionId) return null;
|
|
115
118
|
|
|
116
119
|
const cached = cache.get(sessionId);
|
|
117
|
-
if (
|
|
120
|
+
if (
|
|
121
|
+
cached &&
|
|
122
|
+
(cached.directory !== null || Date.now() - cached.recordedAt < FAILED_LOOKUP_RETRY_MS)
|
|
123
|
+
) {
|
|
118
124
|
// Refresh LRU position
|
|
119
125
|
cache.delete(sessionId);
|
|
120
126
|
cache.set(sessionId, cached);
|
|
121
127
|
return cached.directory;
|
|
122
128
|
}
|
|
123
129
|
|
|
130
|
+
const pending = inflight.get(sessionId);
|
|
131
|
+
if (pending) return pending;
|
|
132
|
+
const lookup = boundedLookup(client, sessionId, fallbackDirectory).finally(() => {
|
|
133
|
+
inflight.delete(sessionId);
|
|
134
|
+
});
|
|
135
|
+
inflight.set(sessionId, lookup);
|
|
136
|
+
return lookup;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/** Ignore late answers: a timed-out lookup must not overwrite a later retry. */
|
|
140
|
+
async function boundedLookup(
|
|
141
|
+
client: unknown,
|
|
142
|
+
sessionId: string,
|
|
143
|
+
fallbackDirectory: string,
|
|
144
|
+
): Promise<string | null> {
|
|
145
|
+
let timer: ReturnType<typeof setTimeout> | undefined;
|
|
146
|
+
try {
|
|
147
|
+
const timeout = new Promise<null>((resolve) => {
|
|
148
|
+
timer = setTimeout(() => {
|
|
149
|
+
sessionWarn(
|
|
150
|
+
sessionId,
|
|
151
|
+
`[aft-plugin] session lookup did not answer within ${LOOKUP_TIMEOUT_MS}ms; using the call's directory for now`,
|
|
152
|
+
);
|
|
153
|
+
resolve(null);
|
|
154
|
+
}, LOOKUP_TIMEOUT_MS);
|
|
155
|
+
});
|
|
156
|
+
const directory = await Promise.race([
|
|
157
|
+
lookupSessionDirectory(client, sessionId, fallbackDirectory),
|
|
158
|
+
timeout,
|
|
159
|
+
]);
|
|
160
|
+
setCache(sessionId, directory);
|
|
161
|
+
return directory;
|
|
162
|
+
} finally {
|
|
163
|
+
clearTimeout(timer);
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
async function lookupSessionDirectory(
|
|
168
|
+
client: unknown,
|
|
169
|
+
sessionId: string,
|
|
170
|
+
fallbackDirectory: string,
|
|
171
|
+
): Promise<string | null> {
|
|
124
172
|
if (isV2PluginContext(client)) return lookupV2SessionDirectory(client, sessionId);
|
|
125
173
|
|
|
126
174
|
const c = client as OpenCodeClientShape;
|
|
127
175
|
const sessionApi = c?.session;
|
|
128
176
|
if (!sessionApi || typeof sessionApi.get !== "function") {
|
|
129
|
-
setCache(sessionId, null);
|
|
130
177
|
return null;
|
|
131
178
|
}
|
|
132
179
|
|
|
@@ -148,7 +195,7 @@ export async function getSessionDirectory(
|
|
|
148
195
|
dir = session.directory;
|
|
149
196
|
}
|
|
150
197
|
} catch (err) {
|
|
151
|
-
//
|
|
198
|
+
// The bounded caller caches failures briefly so a struggling host is not polled per call.
|
|
152
199
|
sessionWarn(
|
|
153
200
|
sessionId,
|
|
154
201
|
`[aft-plugin] session.get lookup failed: ${err instanceof Error ? err.message : String(err)}`,
|
|
@@ -156,7 +203,6 @@ export async function getSessionDirectory(
|
|
|
156
203
|
return null;
|
|
157
204
|
}
|
|
158
205
|
|
|
159
|
-
setCache(sessionId, dir);
|
|
160
206
|
return dir;
|
|
161
207
|
}
|
|
162
208
|
|
|
@@ -164,12 +210,10 @@ export async function getSessionDirectory(
|
|
|
164
210
|
* OpenCode 2 lookup: run the context's `session.get` Effect under a timeout and
|
|
165
211
|
* read `location.directory`.
|
|
166
212
|
*
|
|
167
|
-
*
|
|
168
|
-
*
|
|
169
|
-
*
|
|
170
|
-
*
|
|
171
|
-
* the Location created for that session's own directory. Retrying would only
|
|
172
|
-
* add up to the timeout to every later tool call while the host misbehaves.
|
|
213
|
+
* The outer bounded lookup applies the same retry policy as OpenCode 1.
|
|
214
|
+
* The Effect timeout also interrupts the abandoned V2 lookup; fallback remains
|
|
215
|
+
* the tool call's runtime.directory (the host's Location directory), never
|
|
216
|
+
* the main checkout.
|
|
173
217
|
*/
|
|
174
218
|
async function lookupV2SessionDirectory(
|
|
175
219
|
context: V2PluginContextShape,
|
|
@@ -196,7 +240,6 @@ async function lookupV2SessionDirectory(
|
|
|
196
240
|
`[aft-plugin] OpenCode 2 session lookup failed (${failure}); tool paths and bash use the Location directory ${context.location.directory} for this session`,
|
|
197
241
|
);
|
|
198
242
|
}
|
|
199
|
-
setCache(sessionId, dir);
|
|
200
243
|
return dir;
|
|
201
244
|
}
|
|
202
245
|
|
|
@@ -226,6 +269,10 @@ export function getSessionDirectoryCached(
|
|
|
226
269
|
if (!sessionId) return undefined;
|
|
227
270
|
const cached = cache.get(sessionId);
|
|
228
271
|
if (!cached) return undefined;
|
|
272
|
+
if (cached.directory === null && Date.now() - cached.recordedAt >= FAILED_LOOKUP_RETRY_MS) {
|
|
273
|
+
cache.delete(sessionId);
|
|
274
|
+
return undefined;
|
|
275
|
+
}
|
|
229
276
|
return cached.directory;
|
|
230
277
|
}
|
|
231
278
|
|
|
@@ -240,7 +287,7 @@ export function warmSessionDirectory(
|
|
|
240
287
|
fallbackDirectory: string,
|
|
241
288
|
): void {
|
|
242
289
|
if (!sessionId) return;
|
|
243
|
-
if (
|
|
290
|
+
if (getSessionDirectoryCached(sessionId) !== undefined) return;
|
|
244
291
|
void getSessionDirectory(client, sessionId, fallbackDirectory);
|
|
245
292
|
}
|
|
246
293
|
|
|
@@ -302,5 +349,6 @@ export async function verifySessionDirectory(
|
|
|
302
349
|
/** Test-only: clear the cache between unit tests. */
|
|
303
350
|
export function _resetSessionDirectoryCacheForTest(): void {
|
|
304
351
|
cache.clear();
|
|
352
|
+
inflight.clear();
|
|
305
353
|
verifyCache.clear();
|
|
306
354
|
}
|
package/src/shared/status.ts
CHANGED
|
@@ -55,6 +55,7 @@ export interface StatusCompression {
|
|
|
55
55
|
* absence as a clean zero.
|
|
56
56
|
*/
|
|
57
57
|
export interface StatusBar {
|
|
58
|
+
disabled_categories?: string[];
|
|
58
59
|
errors?: number;
|
|
59
60
|
warnings?: number;
|
|
60
61
|
dead_code?: number;
|
|
@@ -214,6 +215,9 @@ function readCompression(value: unknown): StatusCompression | undefined {
|
|
|
214
215
|
function readStatusBar(value: unknown): StatusBar | undefined {
|
|
215
216
|
if (typeof value !== "object" || value === null) return undefined;
|
|
216
217
|
const bar = asRecord(value);
|
|
218
|
+
const disabled = Array.isArray(bar.disabled_categories)
|
|
219
|
+
? bar.disabled_categories.filter((key): key is string => typeof key === "string")
|
|
220
|
+
: [];
|
|
217
221
|
const errors = readOptionalNumber(bar.errors);
|
|
218
222
|
const warnings = readOptionalNumber(bar.warnings);
|
|
219
223
|
const deadCode = readOptionalNumber(bar.dead_code);
|
|
@@ -228,11 +232,13 @@ function readStatusBar(value: unknown): StatusBar | undefined {
|
|
|
228
232
|
unusedExports === null &&
|
|
229
233
|
duplicates === null &&
|
|
230
234
|
todos === null &&
|
|
231
|
-
!noLanguageServer
|
|
235
|
+
!noLanguageServer &&
|
|
236
|
+
disabled.length === 0
|
|
232
237
|
) {
|
|
233
238
|
return undefined;
|
|
234
239
|
}
|
|
235
240
|
return {
|
|
241
|
+
...(disabled.length > 0 ? { disabled_categories: disabled } : {}),
|
|
236
242
|
...(errors !== null ? { errors } : {}),
|
|
237
243
|
...(warnings !== null ? { warnings } : {}),
|
|
238
244
|
...(deadCode !== null ? { dead_code: deadCode } : {}),
|
|
@@ -437,8 +443,10 @@ export function formatStatusDialogMessage(status: AftStatusSnapshot): string {
|
|
|
437
443
|
lines.push(`- storage dir: ${status.storage_dir ?? status.disk.storage_dir}`);
|
|
438
444
|
}
|
|
439
445
|
|
|
440
|
-
|
|
441
|
-
|
|
446
|
+
const sb = status.status_bar ?? status.status_bar_values;
|
|
447
|
+
if (sb) {
|
|
448
|
+
for (const key of sb.disabled_categories ?? [])
|
|
449
|
+
lines.push(`- ${key.replaceAll("_", " ")}: ○ off`);
|
|
442
450
|
const rows = [
|
|
443
451
|
["errors", sb.errors],
|
|
444
452
|
["warnings", sb.warnings],
|
|
@@ -594,8 +602,10 @@ export function formatStatusMarkdown(status: AftStatusSnapshot): string {
|
|
|
594
602
|
lines.push(`- **Storage dir:** \`${status.storage_dir ?? status.disk.storage_dir}\``);
|
|
595
603
|
}
|
|
596
604
|
|
|
597
|
-
|
|
598
|
-
|
|
605
|
+
const sb = status.status_bar ?? status.status_bar_values;
|
|
606
|
+
if (sb) {
|
|
607
|
+
for (const key of sb.disabled_categories ?? [])
|
|
608
|
+
lines.push(`- **${key.replaceAll("_", " ")}:** ○ off`);
|
|
599
609
|
const rows = [
|
|
600
610
|
["Errors", sb.errors],
|
|
601
611
|
["Warnings", sb.warnings],
|