@cortexkit/aft-opencode 0.39.1 → 0.39.3
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/bg-notifications.d.ts +5 -20
- package/dist/bg-notifications.d.ts.map +1 -1
- package/dist/config.d.ts +23 -10
- package/dist/config.d.ts.map +1 -1
- package/dist/configure-warnings.d.ts +4 -1
- package/dist/configure-warnings.d.ts.map +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +535 -284
- package/dist/notifications.d.ts +1 -1
- package/dist/notifications.d.ts.map +1 -1
- package/dist/tools/bash.d.ts +3 -5
- package/dist/tools/bash.d.ts.map +1 -1
- package/dist/tools/hoisted.d.ts +5 -12
- package/dist/tools/hoisted.d.ts.map +1 -1
- package/dist/tools/navigation.d.ts.map +1 -1
- package/dist/tools/search.d.ts.map +1 -1
- package/dist/tui.js +31 -24
- package/dist/workflow-hints.d.ts +3 -4
- package/dist/workflow-hints.d.ts.map +1 -1
- package/package.json +11 -11
- package/src/tui/index.tsx +17 -14
- package/src/tui/sidebar.tsx +19 -14
- package/src/tui/types/opencode-plugin-tui.d.ts +7 -0
- package/dist/shared/live-server-client.d.ts +0 -81
- package/dist/shared/live-server-client.d.ts.map +0 -1
- package/src/shared/live-server-client.ts +0 -206
package/dist/notifications.d.ts
CHANGED
|
@@ -45,7 +45,7 @@ export interface NotificationOptions {
|
|
|
45
45
|
serverUrl?: string;
|
|
46
46
|
}
|
|
47
47
|
export interface ConfigureWarning {
|
|
48
|
-
kind: "formatter_not_installed" | "checker_not_installed" | "lsp_binary_missing";
|
|
48
|
+
kind: "formatter_not_installed" | "checker_not_installed" | "lsp_binary_missing" | "config_parse_failed";
|
|
49
49
|
language?: string;
|
|
50
50
|
server?: string;
|
|
51
51
|
tool?: string;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"notifications.d.ts","sourceRoot":"","sources":["../src/notifications.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAKH,OAAO,EACL,KAAK,YAAY,EAGlB,MAAM,uBAAuB,CAAC;AAC/B,OAAO,KAAK,EAAE,yBAAyB,EAAE,MAAM,aAAa,CAAC;AA2K7D,wBAAgB,gCAAgC,IAAI,IAAI,CAEvD;AAID,KAAK,UAAU,GAAG;IAChB,IAAI,CAAC,EAAE;QAAE,EAAE,CAAC,EAAE,MAAM,CAAC;QAAC,IAAI,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;IACtC,KAAK,CAAC,EAAE,KAAK,CAAC;QAAE,IAAI,CAAC,EAAE,MAAM,CAAC;QAAC,IAAI,CAAC,EAAE,MAAM,CAAC;QAAC,OAAO,CAAC,EAAE,OAAO,CAAA;KAAE,CAAC,CAAC;CACpE,CAAC;AAyBF,eAAO,MAAM,sBAAsB,KAAK,CAAC;AAEzC;;;;GAIG;AACH,wBAAsB,kBAAkB,CACtC,MAAM,EAAE,OAAO,EACf,SAAS,EAAE,MAAM,GAChB,OAAO,CAAC,UAAU,EAAE,CAAC,CAqBvB;AAkHD,MAAM,WAAW,mBAAmB;IAClC,8BAA8B;IAC9B,MAAM,EAAE,OAAO,CAAC;IAChB,2EAA2E;IAC3E,SAAS,EAAE,MAAM,CAAC;IAClB,yEAAyE;IACzE,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,sEAAsE;IACtE,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED,MAAM,WAAW,gBAAgB;IAC/B,IAAI,
|
|
1
|
+
{"version":3,"file":"notifications.d.ts","sourceRoot":"","sources":["../src/notifications.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAKH,OAAO,EACL,KAAK,YAAY,EAGlB,MAAM,uBAAuB,CAAC;AAC/B,OAAO,KAAK,EAAE,yBAAyB,EAAE,MAAM,aAAa,CAAC;AA2K7D,wBAAgB,gCAAgC,IAAI,IAAI,CAEvD;AAID,KAAK,UAAU,GAAG;IAChB,IAAI,CAAC,EAAE;QAAE,EAAE,CAAC,EAAE,MAAM,CAAC;QAAC,IAAI,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;IACtC,KAAK,CAAC,EAAE,KAAK,CAAC;QAAE,IAAI,CAAC,EAAE,MAAM,CAAC;QAAC,IAAI,CAAC,EAAE,MAAM,CAAC;QAAC,OAAO,CAAC,EAAE,OAAO,CAAA;KAAE,CAAC,CAAC;CACpE,CAAC;AAyBF,eAAO,MAAM,sBAAsB,KAAK,CAAC;AAEzC;;;;GAIG;AACH,wBAAsB,kBAAkB,CACtC,MAAM,EAAE,OAAO,EACf,SAAS,EAAE,MAAM,GAChB,OAAO,CAAC,UAAU,EAAE,CAAC,CAqBvB;AAkHD,MAAM,WAAW,mBAAmB;IAClC,8BAA8B;IAC9B,MAAM,EAAE,OAAO,CAAC;IAChB,2EAA2E;IAC3E,SAAS,EAAE,MAAM,CAAC;IAClB,yEAAyE;IACzE,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,sEAAsE;IACtE,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED,MAAM,WAAW,gBAAgB;IAC/B,IAAI,EACA,yBAAyB,GACzB,uBAAuB,GACvB,oBAAoB,GACpB,qBAAqB,CAAC;IAC1B,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,MAAM,EAAE,IAAI,CAAC,YAAY,EAAE,MAAM,CAAC,CAAC;IACnC,UAAU,EAAE,MAAM,CAAC;IACnB,aAAa,EAAE,MAAM,CAAC;IACtB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,QAAQ,CAAC,EAAE,yBAAyB,CAAC;CACtC;AAED;;;;GAIG;AACH,wBAAsB,WAAW,CAAC,IAAI,EAAE,mBAAmB,EAAE,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAe3F;AAED;;;;GAIG;AACH,wBAAsB,UAAU,CAAC,IAAI,EAAE,mBAAmB,EAAE,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CA2C1F;AAED;;;;;GAKG;AACH,wBAAsB,uBAAuB,CAC3C,IAAI,EAAE,mBAAmB,EACzB,OAAO,EAAE,MAAM,EACf,QAAQ,EAAE,MAAM,EAAE,EAClB,MAAM,EAAE,MAAM,EACd,UAAU,CAAC,EAAE,MAAM,GAClB,OAAO,CAAC,IAAI,CAAC,CA2Cf;AAiLD,wBAAsB,wBAAwB,CAC5C,IAAI,EAAE,uBAAuB,EAC7B,QAAQ,EAAE,gBAAgB,EAAE,GAC3B,OAAO,CAAC,IAAI,CAAC,CA6Cf;AAED;;;GAGG;AACH,wBAAsB,eAAe,CAAC,IAAI,EAAE,mBAAmB,GAAG,OAAO,CAAC,IAAI,CAAC,CA0C9E"}
|
package/dist/tools/bash.d.ts
CHANGED
|
@@ -9,11 +9,9 @@ import type { PluginContext } from "../types.js";
|
|
|
9
9
|
* is known.
|
|
10
10
|
* - the compression sentence only appears when output compression is on —
|
|
11
11
|
* advertising `compressed: false` otherwise would describe a no-op.
|
|
12
|
-
* - the background/PTY sentences only appear when `bash.background` is on
|
|
13
|
-
*
|
|
14
|
-
* `
|
|
15
|
-
* regardless (the flag gates explicit spawning only), so the no-background
|
|
16
|
-
* variant still explains promoted tasks and bash_status/bash_kill.
|
|
12
|
+
* - the background/PTY/watch sentences only appear when `bash.background` is on.
|
|
13
|
+
* With it off, the foreground tool surface runs commands to completion
|
|
14
|
+
* inline and treats `timeout` as the hard kill cap.
|
|
17
15
|
*
|
|
18
16
|
* Wording rules: this is read by AGENTS choosing a tool, not by users reading
|
|
19
17
|
* docs. No internal vocabulary ("hoisted", "command rewriting", "unified bash
|
package/dist/tools/bash.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"bash.d.ts","sourceRoot":"","sources":["../../src/tools/bash.ts"],"names":[],"mappings":"AAWA,OAAO,KAAK,EAAe,cAAc,EAAE,MAAM,qBAAqB,CAAC;AAYvE,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAiCjD
|
|
1
|
+
{"version":3,"file":"bash.d.ts","sourceRoot":"","sources":["../../src/tools/bash.ts"],"names":[],"mappings":"AAWA,OAAO,KAAK,EAAe,cAAc,EAAE,MAAM,qBAAqB,CAAC;AAYvE,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAiCjD;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,mBAAmB,CACjC,mBAAmB,EAAE,OAAO,EAC5B,aAAa,EAAE,OAAO,EACtB,YAAY,EAAE,OAAO,GACpB,MAAM,CAaR;AA6ED,wBAAgB,cAAc,CAC5B,GAAG,EAAE,aAAa,EAClB,2BAA2B,CAAC,EAAE,OAAO,GACpC,cAAc,CA8RhB;AAED,wBAAgB,oBAAoB,CAAC,GAAG,EAAE,aAAa,GAAG,cAAc,CAyBvE;AAED,wBAAgB,kBAAkB,CAAC,GAAG,EAAE,aAAa,GAAG,cAAc,CAuBrE"}
|
package/dist/tools/hoisted.d.ts
CHANGED
|
@@ -20,18 +20,11 @@ export declare function createReadTool(ctx: PluginContext): ToolDefinition;
|
|
|
20
20
|
* Returns hoisted tools keyed by opencode's built-in names.
|
|
21
21
|
* Overrides: read, write, edit, apply_patch (always when hoisting is on).
|
|
22
22
|
*
|
|
23
|
-
* Bash hoisting
|
|
24
|
-
*
|
|
25
|
-
*
|
|
26
|
-
*
|
|
27
|
-
*
|
|
28
|
-
*
|
|
29
|
-
* `bash_status` and `bash_kill` ride alongside `bash` regardless of which
|
|
30
|
-
* experimental flag enabled it: foreground bash auto-promotes long-running
|
|
31
|
-
* tasks to background after a short wait-window (v0.20+), so the agent
|
|
32
|
-
* always needs a way to inspect or kill those promoted tasks. The
|
|
33
|
-
* `experimental.bash.background` flag only gates explicit
|
|
34
|
-
* `bash({ background: true })` spawning, not promotion.
|
|
23
|
+
* Bash hoisting follows the resolved `bash` config. When bash is enabled, the
|
|
24
|
+
* primary `bash` tool is registered. Background control tools (`bash_status`,
|
|
25
|
+
* `bash_write`, `bash_watch`, and `bash_kill`) are registered only when
|
|
26
|
+
* `bash.background` resolves true. With `bash.background: false`, foreground
|
|
27
|
+
* bash runs to completion inline and no background surface is exposed.
|
|
35
28
|
*/
|
|
36
29
|
export declare function hoistedTools(ctx: PluginContext): Record<string, ToolDefinition>;
|
|
37
30
|
/**
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"hoisted.d.ts","sourceRoot":"","sources":["../../src/tools/hoisted.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AASH,OAAO,KAAK,EAAE,cAAc,EAAc,MAAM,qBAAqB,CAAC;AAItE,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAiCjD,wEAAwE;AACxE,eAAO,MAAM,wBAAwB,GAAI,IAAI,MAAM,EAAE,QAAQ,MAAM,EAAE,OAAO,MAAM,KAAG,MAChD,CAAC;AA8YtC;;GAEG;AACH,wBAAgB,cAAc,CAAC,GAAG,EAAE,aAAa,GAAG,cAAc,CAwJjE;AA2qCD
|
|
1
|
+
{"version":3,"file":"hoisted.d.ts","sourceRoot":"","sources":["../../src/tools/hoisted.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AASH,OAAO,KAAK,EAAE,cAAc,EAAc,MAAM,qBAAqB,CAAC;AAItE,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAiCjD,wEAAwE;AACxE,eAAO,MAAM,wBAAwB,GAAI,IAAI,MAAM,EAAE,QAAQ,MAAM,EAAE,OAAO,MAAM,KAAG,MAChD,CAAC;AA8YtC;;GAEG;AACH,wBAAgB,cAAc,CAAC,GAAG,EAAE,aAAa,GAAG,cAAc,CAwJjE;AA2qCD;;;;;;;;;GASG;AACH,wBAAgB,YAAY,CAAC,GAAG,EAAE,aAAa,GAAG,MAAM,CAAC,MAAM,EAAE,cAAc,CAAC,CA0B/E;AAMD;;GAEG;AACH,wBAAgB,gBAAgB,CAAC,GAAG,EAAE,aAAa,GAAG,MAAM,CAAC,MAAM,EAAE,cAAc,CAAC,CAqGnF"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"navigation.d.ts","sourceRoot":"","sources":["../../src/tools/navigation.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"navigation.d.ts","sourceRoot":"","sources":["../../src/tools/navigation.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC;AAE1D,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAqBjD;;GAEG;AACH,wBAAgB,eAAe,CAAC,GAAG,EAAE,aAAa,GAAG,MAAM,CAAC,MAAM,EAAE,cAAc,CAAC,CAkGlF"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"search.d.ts","sourceRoot":"","sources":["../../src/tools/search.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC;AAE1D,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;
|
|
1
|
+
{"version":3,"file":"search.d.ts","sourceRoot":"","sources":["../../src/tools/search.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC;AAE1D,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAuJjD;;;;;;;;;;GAUG;AACH,wBAAgB,eAAe,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,EAAE,CA0BrD;AAED;;GAEG;AACH,wBAAgB,WAAW,CAAC,GAAG,EAAE,aAAa,GAAG,MAAM,CAAC,MAAM,EAAE,cAAc,CAAC,CA4J9E"}
|
package/dist/tui.js
CHANGED
|
@@ -7790,7 +7790,7 @@ var require_src2 = __commonJS((exports, module) => {
|
|
|
7790
7790
|
// src/tui/index.tsx
|
|
7791
7791
|
import { createMemo as createMemo2, createSignal as createSignal2, onCleanup as onCleanup2 } from "solid-js";
|
|
7792
7792
|
// package.json
|
|
7793
|
-
var version = "0.39.
|
|
7793
|
+
var version = "0.39.3";
|
|
7794
7794
|
|
|
7795
7795
|
// src/shared/rpc-client.ts
|
|
7796
7796
|
import { existsSync, readdirSync, readFileSync, unlinkSync } from "node:fs";
|
|
@@ -8584,6 +8584,19 @@ function collapsedCompressionValue(compression) {
|
|
|
8584
8584
|
const pct = original_tokens > 0 ? Math.round(savings_tokens / original_tokens * 100) : 0;
|
|
8585
8585
|
return `${formatCount(savings_tokens)} / ${pct}%`;
|
|
8586
8586
|
}
|
|
8587
|
+
function degradedReasonLabel(reason) {
|
|
8588
|
+
if (reason === "home_root") {
|
|
8589
|
+
return "project root is your home directory";
|
|
8590
|
+
}
|
|
8591
|
+
if (reason.startsWith("search_too_many_files:")) {
|
|
8592
|
+
const threshold = reason.split(":")[1] ?? "20000";
|
|
8593
|
+
return `project exceeds ${threshold} files`;
|
|
8594
|
+
}
|
|
8595
|
+
if (reason === "watcher_unavailable") {
|
|
8596
|
+
return "file watcher unavailable; continuing without live external-change invalidation";
|
|
8597
|
+
}
|
|
8598
|
+
return reason;
|
|
8599
|
+
}
|
|
8587
8600
|
function collapsedHealthLights(statusBar) {
|
|
8588
8601
|
if (!statusBar)
|
|
8589
8602
|
return null;
|
|
@@ -8795,16 +8808,6 @@ var SidebarContent = (props) => {
|
|
|
8795
8808
|
const semanticBytes = () => s()?.disk?.semantic_disk_bytes ?? 0;
|
|
8796
8809
|
const compressionRows = () => formatCompressionSidebarRows(s()?.compression);
|
|
8797
8810
|
const statusBar = () => s()?.status_bar;
|
|
8798
|
-
const degradedReasonLabel = (reason) => {
|
|
8799
|
-
if (reason === "home_root") {
|
|
8800
|
-
return "project root is your home directory";
|
|
8801
|
-
}
|
|
8802
|
-
if (reason.startsWith("search_too_many_files:")) {
|
|
8803
|
-
const threshold = reason.split(":")[1] ?? "20000";
|
|
8804
|
-
return `project exceeds ${threshold} files`;
|
|
8805
|
-
}
|
|
8806
|
-
return reason;
|
|
8807
|
-
};
|
|
8808
8811
|
const degradedSummary = () => {
|
|
8809
8812
|
const snap = s();
|
|
8810
8813
|
if (!snap?.degraded)
|
|
@@ -9647,20 +9650,24 @@ async function showStatusDialog(api) {
|
|
|
9647
9650
|
} catch {
|
|
9648
9651
|
initialError = "AFT is starting up. Status will refresh automatically...";
|
|
9649
9652
|
}
|
|
9650
|
-
|
|
9651
|
-
api.ui.dialog.replace(() => /* @__PURE__ */ jsxDEV2(StatusDialog, {
|
|
9652
|
-
api,
|
|
9653
|
-
client,
|
|
9654
|
-
directory,
|
|
9655
|
-
sessionID,
|
|
9656
|
-
initial,
|
|
9657
|
-
initialError,
|
|
9658
|
-
onClose: () => {
|
|
9659
|
-
api.ui.dialog.setSize("medium");
|
|
9660
|
-
}
|
|
9661
|
-
}, undefined, false, undefined, this), () => {
|
|
9653
|
+
const dismissStatusDialog = () => {
|
|
9662
9654
|
api.ui.dialog.setSize("medium");
|
|
9663
|
-
|
|
9655
|
+
api.ui.dialog.clear();
|
|
9656
|
+
};
|
|
9657
|
+
api.ui.dialog.setSize("large");
|
|
9658
|
+
api.ui.dialog.replace(() => /* @__PURE__ */ jsxDEV2(api.ui.Dialog, {
|
|
9659
|
+
size: "large",
|
|
9660
|
+
onClose: dismissStatusDialog,
|
|
9661
|
+
children: /* @__PURE__ */ jsxDEV2(StatusDialog, {
|
|
9662
|
+
api,
|
|
9663
|
+
client,
|
|
9664
|
+
directory,
|
|
9665
|
+
sessionID,
|
|
9666
|
+
initial,
|
|
9667
|
+
initialError,
|
|
9668
|
+
onClose: dismissStatusDialog
|
|
9669
|
+
}, undefined, false, undefined, this)
|
|
9670
|
+
}, undefined, false, undefined, this), dismissStatusDialog);
|
|
9664
9671
|
}
|
|
9665
9672
|
function registerStatusCommand(api) {
|
|
9666
9673
|
const apiAny = api;
|
package/dist/workflow-hints.d.ts
CHANGED
|
@@ -6,7 +6,7 @@ export interface WorkflowHintsOpts {
|
|
|
6
6
|
hoistBuiltins: boolean;
|
|
7
7
|
/** `experimental.semantic_search` — gates `aft_search` mention. */
|
|
8
8
|
semanticEnabled: boolean;
|
|
9
|
-
/** `
|
|
9
|
+
/** `bash.background` — gates background-bash paragraph. */
|
|
10
10
|
bashBackgroundEnabled: boolean;
|
|
11
11
|
/** Resolved bash compression flag. */
|
|
12
12
|
bashCompressionEnabled: boolean;
|
|
@@ -23,9 +23,8 @@ export declare function buildWorkflowHints(opts: WorkflowHintsOpts): string | nu
|
|
|
23
23
|
* Resolve workflow-hints opts from a loaded AftConfig and the active
|
|
24
24
|
* disabled-tools set computed at registration time.
|
|
25
25
|
*
|
|
26
|
-
* Background-bash gating reads the resolved bash config so the
|
|
27
|
-
*
|
|
28
|
-
* hint, not just the legacy `experimental.bash.background: true` path.
|
|
26
|
+
* Background-bash gating reads the resolved bash config so the graduated
|
|
27
|
+
* `bash.background` setting controls whether the hint appears.
|
|
29
28
|
*/
|
|
30
29
|
export declare function buildHintsFromConfig(config: AftConfig, disabledTools: Set<string>): string | null;
|
|
31
30
|
//# sourceMappingURL=workflow-hints.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"workflow-hints.d.ts","sourceRoot":"","sources":["../src/workflow-hints.ts"],"names":[],"mappings":"AAUA,OAAO,EAAE,KAAK,SAAS,EAAqB,MAAM,aAAa,CAAC;AAEhE,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,
|
|
1
|
+
{"version":3,"file":"workflow-hints.d.ts","sourceRoot":"","sources":["../src/workflow-hints.ts"],"names":[],"mappings":"AAUA,OAAO,EAAE,KAAK,SAAS,EAAqB,MAAM,aAAa,CAAC;AAEhE,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,2DAA2D;IAC3D,qBAAqB,EAAE,OAAO,CAAC;IAC/B,sCAAsC;IACtC,sBAAsB,EAAE,OAAO,CAAC;IAChC,4DAA4D;IAC5D,aAAa,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC;CAC5B;AAID;;;;GAIG;AACH,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,iBAAiB,GAAG,MAAM,GAAG,IAAI,CAkIzE;AAED;;;;;;GAMG;AACH,wBAAgB,oBAAoB,CAAC,MAAM,EAAE,SAAS,EAAE,aAAa,EAAE,GAAG,CAAC,MAAM,CAAC,GAAG,MAAM,GAAG,IAAI,CASjG"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cortexkit/aft-opencode",
|
|
3
|
-
"version": "0.39.
|
|
3
|
+
"version": "0.39.3",
|
|
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",
|
|
@@ -30,23 +30,23 @@
|
|
|
30
30
|
},
|
|
31
31
|
"dependencies": {
|
|
32
32
|
"@clack/prompts": "^1.2.0",
|
|
33
|
-
"@cortexkit/aft-bridge": "0.39.
|
|
33
|
+
"@cortexkit/aft-bridge": "0.39.3",
|
|
34
34
|
"@xterm/headless": "^5.5.0",
|
|
35
35
|
"comment-json": "^4.6.2",
|
|
36
36
|
"undici": "^7.25.0",
|
|
37
37
|
"zod": "^4.1.8"
|
|
38
38
|
},
|
|
39
39
|
"optionalDependencies": {
|
|
40
|
-
"@cortexkit/aft-darwin-arm64": "0.39.
|
|
41
|
-
"@cortexkit/aft-darwin-x64": "0.39.
|
|
42
|
-
"@cortexkit/aft-linux-arm64": "0.39.
|
|
43
|
-
"@cortexkit/aft-linux-x64": "0.39.
|
|
44
|
-
"@cortexkit/aft-win32-arm64": "0.39.
|
|
45
|
-
"@cortexkit/aft-win32-x64": "0.39.
|
|
40
|
+
"@cortexkit/aft-darwin-arm64": "0.39.3",
|
|
41
|
+
"@cortexkit/aft-darwin-x64": "0.39.3",
|
|
42
|
+
"@cortexkit/aft-linux-arm64": "0.39.3",
|
|
43
|
+
"@cortexkit/aft-linux-x64": "0.39.3",
|
|
44
|
+
"@cortexkit/aft-win32-arm64": "0.39.3",
|
|
45
|
+
"@cortexkit/aft-win32-x64": "0.39.3"
|
|
46
46
|
},
|
|
47
47
|
"devDependencies": {
|
|
48
|
-
"@opencode-ai/plugin": "^1.
|
|
49
|
-
"@opencode-ai/sdk": "^1.
|
|
48
|
+
"@opencode-ai/plugin": "^1.17.7",
|
|
49
|
+
"@opencode-ai/sdk": "^1.17.7",
|
|
50
50
|
"@types/node": "^22.0.0",
|
|
51
51
|
"typescript": "^5.8.0"
|
|
52
52
|
},
|
|
@@ -65,6 +65,6 @@
|
|
|
65
65
|
"tui"
|
|
66
66
|
],
|
|
67
67
|
"peerDependencies": {
|
|
68
|
-
"@opencode-ai/plugin": ">=1.
|
|
68
|
+
"@opencode-ai/plugin": ">=1.17.0"
|
|
69
69
|
}
|
|
70
70
|
}
|
package/src/tui/index.tsx
CHANGED
|
@@ -576,24 +576,27 @@ async function showStatusDialog(api: TuiPluginApi): Promise<void> {
|
|
|
576
576
|
initialError = "AFT is starting up. Status will refresh automatically...";
|
|
577
577
|
}
|
|
578
578
|
|
|
579
|
+
const dismissStatusDialog = () => {
|
|
580
|
+
api.ui.dialog.setSize("medium");
|
|
581
|
+
api.ui.dialog.clear();
|
|
582
|
+
};
|
|
583
|
+
|
|
579
584
|
api.ui.dialog.setSize("large");
|
|
580
585
|
api.ui.dialog.replace(
|
|
581
586
|
() => (
|
|
582
|
-
<
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
587
|
+
<api.ui.Dialog size="large" onClose={dismissStatusDialog}>
|
|
588
|
+
<StatusDialog
|
|
589
|
+
api={api}
|
|
590
|
+
client={client}
|
|
591
|
+
directory={directory}
|
|
592
|
+
sessionID={sessionID}
|
|
593
|
+
initial={initial}
|
|
594
|
+
initialError={initialError}
|
|
595
|
+
onClose={dismissStatusDialog}
|
|
596
|
+
/>
|
|
597
|
+
</api.ui.Dialog>
|
|
593
598
|
),
|
|
594
|
-
|
|
595
|
-
api.ui.dialog.setSize("medium");
|
|
596
|
-
},
|
|
599
|
+
dismissStatusDialog,
|
|
597
600
|
);
|
|
598
601
|
}
|
|
599
602
|
|
package/src/tui/sidebar.tsx
CHANGED
|
@@ -194,6 +194,25 @@ export function collapsedCompressionValue(
|
|
|
194
194
|
|
|
195
195
|
export type HealthLightTone = "ok" | "warn" | "err";
|
|
196
196
|
|
|
197
|
+
// Degraded-mode reason → human-readable hint. Distinct strings per reason
|
|
198
|
+
// because the UX direction is different: "home_root" tells the user to open a
|
|
199
|
+
// real project subdirectory, "search_too_many_files" tells them the tree is too
|
|
200
|
+
// big for full indexing, and "watcher_unavailable" is an honest soft
|
|
201
|
+
// degradation (AFT continues without live external-change invalidation).
|
|
202
|
+
export function degradedReasonLabel(reason: string): string {
|
|
203
|
+
if (reason === "home_root") {
|
|
204
|
+
return "project root is your home directory";
|
|
205
|
+
}
|
|
206
|
+
if (reason.startsWith("search_too_many_files:")) {
|
|
207
|
+
const threshold = reason.split(":")[1] ?? "20000";
|
|
208
|
+
return `project exceeds ${threshold} files`;
|
|
209
|
+
}
|
|
210
|
+
if (reason === "watcher_unavailable") {
|
|
211
|
+
return "file watcher unavailable; continuing without live external-change invalidation";
|
|
212
|
+
}
|
|
213
|
+
return reason; // unknown reason — surface verbatim so users can grep logs
|
|
214
|
+
}
|
|
215
|
+
|
|
197
216
|
export interface HealthLights {
|
|
198
217
|
// Diagnostics: red if any errors, yellow if any warnings, else green.
|
|
199
218
|
diagnostics: HealthLightTone;
|
|
@@ -543,20 +562,6 @@ const SidebarContent = (props: {
|
|
|
543
562
|
const compressionRows = () => formatCompressionSidebarRows(s()?.compression);
|
|
544
563
|
const statusBar = () => s()?.status_bar;
|
|
545
564
|
|
|
546
|
-
// Degraded-mode reason → human-readable hint. Distinct strings per reason
|
|
547
|
-
// because the UX direction is different: "home_root" tells the user to
|
|
548
|
-
// open a real project subdirectory, "search_too_many_files" tells them the
|
|
549
|
-
// tree is too big for full indexing.
|
|
550
|
-
const degradedReasonLabel = (reason: string): string => {
|
|
551
|
-
if (reason === "home_root") {
|
|
552
|
-
return "project root is your home directory";
|
|
553
|
-
}
|
|
554
|
-
if (reason.startsWith("search_too_many_files:")) {
|
|
555
|
-
const threshold = reason.split(":")[1] ?? "20000";
|
|
556
|
-
return `project exceeds ${threshold} files`;
|
|
557
|
-
}
|
|
558
|
-
return reason; // unknown reason — surface verbatim so users can grep logs
|
|
559
|
-
};
|
|
560
565
|
const degradedSummary = () => {
|
|
561
566
|
const snap = s();
|
|
562
567
|
if (!snap?.degraded) return null;
|
|
@@ -78,6 +78,12 @@ declare module "@opencode-ai/plugin/tui" {
|
|
|
78
78
|
duration?: number;
|
|
79
79
|
};
|
|
80
80
|
|
|
81
|
+
export type TuiDialogProps = {
|
|
82
|
+
size?: "medium" | "large" | "xlarge";
|
|
83
|
+
onClose: () => void;
|
|
84
|
+
children?: JSX.Element;
|
|
85
|
+
};
|
|
86
|
+
|
|
81
87
|
export type TuiDialogStack = {
|
|
82
88
|
replace: (render: () => JSX.Element, onClose?: () => void) => void;
|
|
83
89
|
clear: () => void;
|
|
@@ -198,6 +204,7 @@ declare module "@opencode-ai/plugin/tui" {
|
|
|
198
204
|
| { name: string; params?: Record<string, unknown> };
|
|
199
205
|
};
|
|
200
206
|
ui: {
|
|
207
|
+
Dialog: (props: TuiDialogProps) => JSX.Element;
|
|
201
208
|
DialogAlert: (props: TuiDialogAlertProps) => JSX.Element;
|
|
202
209
|
DialogConfirm: (props: TuiDialogConfirmProps) => JSX.Element;
|
|
203
210
|
DialogPrompt: (props: TuiDialogPromptProps) => JSX.Element;
|
|
@@ -1,81 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Workaround helper for the OpenCode plugin promptAsync runner-split bug
|
|
3
|
-
* (https://github.com/anomalyco/opencode/issues/28202).
|
|
4
|
-
*
|
|
5
|
-
* OpenCode's plugin-provided `input.client` is constructed with
|
|
6
|
-
* `fetch: async (...args) => Server.Default().app.fetch(...args)`, which
|
|
7
|
-
* routes requests through `HttpApiApp.webHandler()` and a SEPARATE Effect
|
|
8
|
-
* `memoMap` from the one used by the live HTTP listener. Since
|
|
9
|
-
* `SessionRunState` is a per-memo-map in-memory layer, plugin-origin
|
|
10
|
-
* `promptAsync` calls observe an "idle" runner while the live UI turn is
|
|
11
|
-
* still running. The result is that `ensureRunning` fails to coalesce and
|
|
12
|
-
* OpenCode persists multiple assistant children under a single synthetic
|
|
13
|
-
* user parent — what users see as duplicate "stop" messages after every
|
|
14
|
-
* background-bash completion reminder.
|
|
15
|
-
*
|
|
16
|
-
* The workaround is to bypass `input.client` for the wake path and build
|
|
17
|
-
* a separate `createOpencodeClient` configured to hit `input.serverUrl`
|
|
18
|
-
* via `globalThis.fetch`. That client enters the same live listener the
|
|
19
|
-
* UI uses, so the active session's `SessionRunState` is the one that
|
|
20
|
-
* resolves `ensureRunning` and overlapping turns coalesce correctly.
|
|
21
|
-
*
|
|
22
|
-
* The workaround only works when the live HTTP listener is actually
|
|
23
|
-
* reachable. OpenCode Desktop (Electron+Node) and TUI launched with
|
|
24
|
-
* `opencode --port 0` bind a real API listener; plain TUI binds an
|
|
25
|
-
* internal-only listener that 404s for `/session/*`. We probe once at
|
|
26
|
-
* plugin init and cache the result by `serverUrl`. When that server is
|
|
27
|
-
* unreachable, the wake path silently uses the in-process
|
|
28
|
-
* `input.client.session.promptAsync`, which keeps wakes flowing (at the
|
|
29
|
-
* cost of the upstream duplicate-runner bug) instead of producing no
|
|
30
|
-
* notification at all or nagging the user to relaunch with a different
|
|
31
|
-
* flag.
|
|
32
|
-
*
|
|
33
|
-
* Tracked upstream as anomalyco/opencode#28202. When OpenCode fixes the
|
|
34
|
-
* runtime split, this helper and its single consumer in `bg-notifications.ts`
|
|
35
|
-
* can be deleted and the wake path can go back to `input.client`.
|
|
36
|
-
*/
|
|
37
|
-
import { createOpencodeClient } from "@opencode-ai/sdk";
|
|
38
|
-
export type LiveServerClient = ReturnType<typeof createOpencodeClient>;
|
|
39
|
-
/**
|
|
40
|
-
* Return a cached `createOpencodeClient` pointed at the live HTTP listener
|
|
41
|
-
* for the given `(serverUrl, directory)` pair. One client object is reused
|
|
42
|
-
* across many wakes for a given session.
|
|
43
|
-
*
|
|
44
|
-
* The `fetch` is bound to `globalThis.fetch` explicitly. Without this, the
|
|
45
|
-
* SDK would fall back to `globalThis.fetch` anyway in normal Node runtimes,
|
|
46
|
-
* but we set it on purpose so anyone reading this code (or grepping for the
|
|
47
|
-
* bug fix) can see that we intentionally chose the live HTTP transport.
|
|
48
|
-
*/
|
|
49
|
-
export declare function getLiveServerClient(serverUrl: string, directory: string): LiveServerClient;
|
|
50
|
-
/** Test helper — drop the cache between cases so each test starts clean. */
|
|
51
|
-
export declare function __resetLiveServerClientCacheForTests(): void;
|
|
52
|
-
/**
|
|
53
|
-
* Probe whether `serverUrl` serves OpenCode's HTTP API within `timeoutMs`.
|
|
54
|
-
* Returns `true` only when `/session` proves the API is usable: any 2xx
|
|
55
|
-
* response is reachable, and 401/403 also count as reachable because an
|
|
56
|
-
* auth-protected listener still exists. Returns `false` for 404 (plain
|
|
57
|
-
* TUI's internal listener), 5xx, connection refused, DNS failure, timeout,
|
|
58
|
-
* malformed URL, or undefined URL.
|
|
59
|
-
*
|
|
60
|
-
* The probe records its result in the per-`serverUrl` wake-availability
|
|
61
|
-
* cache. That keeps multiple OpenCode windows with different live listener
|
|
62
|
-
* URLs from sharing one process-global transport decision.
|
|
63
|
-
*/
|
|
64
|
-
export declare function probeServerReachable(serverUrl: string | undefined, timeoutMs?: number): Promise<boolean>;
|
|
65
|
-
/**
|
|
66
|
-
* Record a probe result. Prefer the `(serverUrl, available)` form; the
|
|
67
|
-
* single-boolean form is a compatibility fallback for old call sites and
|
|
68
|
-
* tests that do not yet have a URL to key by.
|
|
69
|
-
*/
|
|
70
|
-
export declare function setLiveServerWakeAvailable(available: boolean): void;
|
|
71
|
-
export declare function setLiveServerWakeAvailable(serverUrl: string | undefined, available: boolean): void;
|
|
72
|
-
/**
|
|
73
|
-
* Read the cached probe decision for `serverUrl`. `true` means the wake path
|
|
74
|
-
* should use `getLiveServerClient(serverUrl, directory)` and POST through
|
|
75
|
-
* the live HTTP listener. `false` means fall back to the in-process client
|
|
76
|
-
* passed via plugin context (`input.client`).
|
|
77
|
-
*/
|
|
78
|
-
export declare function useLiveServerWake(serverUrl?: string): boolean;
|
|
79
|
-
/** Test helper — reset the decision cache between cases. */
|
|
80
|
-
export declare function __resetLiveServerWakeForTests(): void;
|
|
81
|
-
//# sourceMappingURL=live-server-client.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"live-server-client.d.ts","sourceRoot":"","sources":["../../src/shared/live-server-client.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAmCG;AAEH,OAAO,EAAE,oBAAoB,EAAE,MAAM,kBAAkB,CAAC;AAExD,MAAM,MAAM,gBAAgB,GAAG,UAAU,CAAC,OAAO,oBAAoB,CAAC,CAAC;AAqCvE;;;;;;;;;GASG;AACH,wBAAgB,mBAAmB,CAAC,SAAS,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,gBAAgB,CAY1F;AAED,4EAA4E;AAC5E,wBAAgB,oCAAoC,IAAI,IAAI,CAE3D;AAmBD;;;;;;;;;;;GAWG;AACH,wBAAsB,oBAAoB,CACxC,SAAS,EAAE,MAAM,GAAG,SAAS,EAC7B,SAAS,SAAO,GACf,OAAO,CAAC,OAAO,CAAC,CA0BlB;AAED;;;;GAIG;AACH,wBAAgB,0BAA0B,CAAC,SAAS,EAAE,OAAO,GAAG,IAAI,CAAC;AACrE,wBAAgB,0BAA0B,CAAC,SAAS,EAAE,MAAM,GAAG,SAAS,EAAE,SAAS,EAAE,OAAO,GAAG,IAAI,CAAC;AAmBpG;;;;;GAKG;AACH,wBAAgB,iBAAiB,CAAC,SAAS,CAAC,EAAE,MAAM,GAAG,OAAO,CAG7D;AAED,4DAA4D;AAC5D,wBAAgB,6BAA6B,IAAI,IAAI,CAGpD"}
|
|
@@ -1,206 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Workaround helper for the OpenCode plugin promptAsync runner-split bug
|
|
3
|
-
* (https://github.com/anomalyco/opencode/issues/28202).
|
|
4
|
-
*
|
|
5
|
-
* OpenCode's plugin-provided `input.client` is constructed with
|
|
6
|
-
* `fetch: async (...args) => Server.Default().app.fetch(...args)`, which
|
|
7
|
-
* routes requests through `HttpApiApp.webHandler()` and a SEPARATE Effect
|
|
8
|
-
* `memoMap` from the one used by the live HTTP listener. Since
|
|
9
|
-
* `SessionRunState` is a per-memo-map in-memory layer, plugin-origin
|
|
10
|
-
* `promptAsync` calls observe an "idle" runner while the live UI turn is
|
|
11
|
-
* still running. The result is that `ensureRunning` fails to coalesce and
|
|
12
|
-
* OpenCode persists multiple assistant children under a single synthetic
|
|
13
|
-
* user parent — what users see as duplicate "stop" messages after every
|
|
14
|
-
* background-bash completion reminder.
|
|
15
|
-
*
|
|
16
|
-
* The workaround is to bypass `input.client` for the wake path and build
|
|
17
|
-
* a separate `createOpencodeClient` configured to hit `input.serverUrl`
|
|
18
|
-
* via `globalThis.fetch`. That client enters the same live listener the
|
|
19
|
-
* UI uses, so the active session's `SessionRunState` is the one that
|
|
20
|
-
* resolves `ensureRunning` and overlapping turns coalesce correctly.
|
|
21
|
-
*
|
|
22
|
-
* The workaround only works when the live HTTP listener is actually
|
|
23
|
-
* reachable. OpenCode Desktop (Electron+Node) and TUI launched with
|
|
24
|
-
* `opencode --port 0` bind a real API listener; plain TUI binds an
|
|
25
|
-
* internal-only listener that 404s for `/session/*`. We probe once at
|
|
26
|
-
* plugin init and cache the result by `serverUrl`. When that server is
|
|
27
|
-
* unreachable, the wake path silently uses the in-process
|
|
28
|
-
* `input.client.session.promptAsync`, which keeps wakes flowing (at the
|
|
29
|
-
* cost of the upstream duplicate-runner bug) instead of producing no
|
|
30
|
-
* notification at all or nagging the user to relaunch with a different
|
|
31
|
-
* flag.
|
|
32
|
-
*
|
|
33
|
-
* Tracked upstream as anomalyco/opencode#28202. When OpenCode fixes the
|
|
34
|
-
* runtime split, this helper and its single consumer in `bg-notifications.ts`
|
|
35
|
-
* can be deleted and the wake path can go back to `input.client`.
|
|
36
|
-
*/
|
|
37
|
-
|
|
38
|
-
import { createOpencodeClient } from "@opencode-ai/sdk";
|
|
39
|
-
|
|
40
|
-
export type LiveServerClient = ReturnType<typeof createOpencodeClient>;
|
|
41
|
-
|
|
42
|
-
/**
|
|
43
|
-
* Cache key is `${serverUrl}|${directory}`. Both are stable per OpenCode
|
|
44
|
-
* session/project pair, so one client is reused across many wakes. We don't
|
|
45
|
-
* key on `serverUrl + auth header` because the auth env vars are server-wide
|
|
46
|
-
* — if they change we'd want a fresh client anyway; in practice they're set
|
|
47
|
-
* once at process start.
|
|
48
|
-
*/
|
|
49
|
-
const clientCache = new Map<string, LiveServerClient>();
|
|
50
|
-
|
|
51
|
-
function cacheKey(serverUrl: string, directory: string): string {
|
|
52
|
-
return `${serverUrl}|${directory}`;
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
function normalizeServerUrl(serverUrl: string): string {
|
|
56
|
-
try {
|
|
57
|
-
return new URL(serverUrl).toString();
|
|
58
|
-
} catch {
|
|
59
|
-
return serverUrl;
|
|
60
|
-
}
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
/**
|
|
64
|
-
* Build the Basic-auth header OpenCode's server expects when
|
|
65
|
-
* `OPENCODE_SERVER_PASSWORD` is set. Read at call time (not at module load)
|
|
66
|
-
* so test setup can mutate `process.env` between cases.
|
|
67
|
-
*/
|
|
68
|
-
function serverAuthHeaders(): Record<string, string> | undefined {
|
|
69
|
-
const password = process.env.OPENCODE_SERVER_PASSWORD;
|
|
70
|
-
if (!password) return undefined;
|
|
71
|
-
const username = process.env.OPENCODE_SERVER_USERNAME ?? "opencode";
|
|
72
|
-
return {
|
|
73
|
-
Authorization: `Basic ${Buffer.from(`${username}:${password}`).toString("base64")}`,
|
|
74
|
-
};
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
/**
|
|
78
|
-
* Return a cached `createOpencodeClient` pointed at the live HTTP listener
|
|
79
|
-
* for the given `(serverUrl, directory)` pair. One client object is reused
|
|
80
|
-
* across many wakes for a given session.
|
|
81
|
-
*
|
|
82
|
-
* The `fetch` is bound to `globalThis.fetch` explicitly. Without this, the
|
|
83
|
-
* SDK would fall back to `globalThis.fetch` anyway in normal Node runtimes,
|
|
84
|
-
* but we set it on purpose so anyone reading this code (or grepping for the
|
|
85
|
-
* bug fix) can see that we intentionally chose the live HTTP transport.
|
|
86
|
-
*/
|
|
87
|
-
export function getLiveServerClient(serverUrl: string, directory: string): LiveServerClient {
|
|
88
|
-
const key = cacheKey(serverUrl, directory);
|
|
89
|
-
const cached = clientCache.get(key);
|
|
90
|
-
if (cached) return cached;
|
|
91
|
-
const client = createOpencodeClient({
|
|
92
|
-
baseUrl: serverUrl,
|
|
93
|
-
directory,
|
|
94
|
-
headers: serverAuthHeaders(),
|
|
95
|
-
fetch: globalThis.fetch,
|
|
96
|
-
});
|
|
97
|
-
clientCache.set(key, client);
|
|
98
|
-
return client;
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
/** Test helper — drop the cache between cases so each test starts clean. */
|
|
102
|
-
export function __resetLiveServerClientCacheForTests(): void {
|
|
103
|
-
clientCache.clear();
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
/**
|
|
107
|
-
* Per-server decision: should bg-notifications use the live-server wake
|
|
108
|
-
* transport (workaround for anomalyco/opencode#28202), or fall back to the
|
|
109
|
-
* in-process `input.client.session.promptAsync` path?
|
|
110
|
-
*
|
|
111
|
-
* Keying by `serverUrl` matters because one plugin process can host
|
|
112
|
-
* multiple OpenCode windows with different live listener URLs. A missing
|
|
113
|
-
* keyed decision defaults to `false` for safety: the in-process client is
|
|
114
|
-
* part of the plugin contract, while the live-server path requires a
|
|
115
|
-
* probe-confirmed listener.
|
|
116
|
-
*
|
|
117
|
-
* `legacyLiveServerWakeAvailable` is retained only for older callers/tests
|
|
118
|
-
* that do not pass a `serverUrl`; keyed wake callers never read it.
|
|
119
|
-
*/
|
|
120
|
-
const liveServerWakeAvailableByServerUrl = new Map<string, boolean>();
|
|
121
|
-
let legacyLiveServerWakeAvailable = false;
|
|
122
|
-
|
|
123
|
-
/**
|
|
124
|
-
* Probe whether `serverUrl` serves OpenCode's HTTP API within `timeoutMs`.
|
|
125
|
-
* Returns `true` only when `/session` proves the API is usable: any 2xx
|
|
126
|
-
* response is reachable, and 401/403 also count as reachable because an
|
|
127
|
-
* auth-protected listener still exists. Returns `false` for 404 (plain
|
|
128
|
-
* TUI's internal listener), 5xx, connection refused, DNS failure, timeout,
|
|
129
|
-
* malformed URL, or undefined URL.
|
|
130
|
-
*
|
|
131
|
-
* The probe records its result in the per-`serverUrl` wake-availability
|
|
132
|
-
* cache. That keeps multiple OpenCode windows with different live listener
|
|
133
|
-
* URLs from sharing one process-global transport decision.
|
|
134
|
-
*/
|
|
135
|
-
export async function probeServerReachable(
|
|
136
|
-
serverUrl: string | undefined,
|
|
137
|
-
timeoutMs = 1500,
|
|
138
|
-
): Promise<boolean> {
|
|
139
|
-
if (!serverUrl) return false;
|
|
140
|
-
const normalizedServerUrl = normalizeServerUrl(serverUrl);
|
|
141
|
-
const controller = new AbortController();
|
|
142
|
-
const timer = setTimeout(() => controller.abort(), timeoutMs);
|
|
143
|
-
let reachable = false;
|
|
144
|
-
try {
|
|
145
|
-
// Hit a path that actually exists on the OpenCode HTTP API so a
|
|
146
|
-
// successful response confirms the API server is up, not just any
|
|
147
|
-
// random listener (e.g. an internal IPC port that accepts TCP but
|
|
148
|
-
// rejects all paths with 404 — exactly what TUI binds without
|
|
149
|
-
// `--port 0`).
|
|
150
|
-
const probeUrl = new URL("/session", serverUrl).toString();
|
|
151
|
-
const res = await globalThis.fetch(probeUrl, {
|
|
152
|
-
method: "GET",
|
|
153
|
-
headers: serverAuthHeaders(),
|
|
154
|
-
signal: controller.signal,
|
|
155
|
-
});
|
|
156
|
-
reachable = res.ok || res.status === 401 || res.status === 403;
|
|
157
|
-
} catch {
|
|
158
|
-
reachable = false;
|
|
159
|
-
} finally {
|
|
160
|
-
clearTimeout(timer);
|
|
161
|
-
liveServerWakeAvailableByServerUrl.set(normalizedServerUrl, reachable);
|
|
162
|
-
}
|
|
163
|
-
return reachable;
|
|
164
|
-
}
|
|
165
|
-
|
|
166
|
-
/**
|
|
167
|
-
* Record a probe result. Prefer the `(serverUrl, available)` form; the
|
|
168
|
-
* single-boolean form is a compatibility fallback for old call sites and
|
|
169
|
-
* tests that do not yet have a URL to key by.
|
|
170
|
-
*/
|
|
171
|
-
export function setLiveServerWakeAvailable(available: boolean): void;
|
|
172
|
-
export function setLiveServerWakeAvailable(serverUrl: string | undefined, available: boolean): void;
|
|
173
|
-
export function setLiveServerWakeAvailable(
|
|
174
|
-
serverUrlOrAvailable: string | boolean | undefined,
|
|
175
|
-
available?: boolean,
|
|
176
|
-
): void {
|
|
177
|
-
if (typeof serverUrlOrAvailable === "boolean") {
|
|
178
|
-
legacyLiveServerWakeAvailable = serverUrlOrAvailable;
|
|
179
|
-
return;
|
|
180
|
-
}
|
|
181
|
-
if (!serverUrlOrAvailable) {
|
|
182
|
-
legacyLiveServerWakeAvailable = available ?? false;
|
|
183
|
-
return;
|
|
184
|
-
}
|
|
185
|
-
liveServerWakeAvailableByServerUrl.set(
|
|
186
|
-
normalizeServerUrl(serverUrlOrAvailable),
|
|
187
|
-
available ?? false,
|
|
188
|
-
);
|
|
189
|
-
}
|
|
190
|
-
|
|
191
|
-
/**
|
|
192
|
-
* Read the cached probe decision for `serverUrl`. `true` means the wake path
|
|
193
|
-
* should use `getLiveServerClient(serverUrl, directory)` and POST through
|
|
194
|
-
* the live HTTP listener. `false` means fall back to the in-process client
|
|
195
|
-
* passed via plugin context (`input.client`).
|
|
196
|
-
*/
|
|
197
|
-
export function useLiveServerWake(serverUrl?: string): boolean {
|
|
198
|
-
if (!serverUrl) return legacyLiveServerWakeAvailable;
|
|
199
|
-
return liveServerWakeAvailableByServerUrl.get(normalizeServerUrl(serverUrl)) ?? false;
|
|
200
|
-
}
|
|
201
|
-
|
|
202
|
-
/** Test helper — reset the decision cache between cases. */
|
|
203
|
-
export function __resetLiveServerWakeForTests(): void {
|
|
204
|
-
liveServerWakeAvailableByServerUrl.clear();
|
|
205
|
-
legacyLiveServerWakeAvailable = false;
|
|
206
|
-
}
|