@cortexkit/aft-opencode 0.28.0 → 0.28.1
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 +10 -0
- package/dist/bg-notifications.d.ts.map +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +24385 -2000
- package/dist/shared/live-server-client.d.ts +60 -0
- package/dist/shared/live-server-client.d.ts.map +1 -0
- package/dist/shared/rpc-client.d.ts +18 -8
- package/dist/shared/rpc-client.d.ts.map +1 -1
- package/dist/shared/rpc-server.d.ts +3 -0
- package/dist/shared/rpc-server.d.ts.map +1 -1
- package/dist/shared/rpc-utils.d.ts +19 -1
- package/dist/shared/rpc-utils.d.ts.map +1 -1
- package/dist/tui.js +101 -87
- package/package.json +9 -9
- package/src/shared/live-server-client.ts +132 -0
- package/src/shared/rpc-client.ts +97 -76
- package/src/shared/rpc-server.ts +8 -3
- package/src/shared/rpc-utils.ts +23 -1
|
@@ -0,0 +1,60 @@
|
|
|
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
|
+
* Tracked upstream as anomalyco/opencode#28202. When OpenCode fixes the
|
|
23
|
+
* runtime split, this helper and its single consumer in `bg-notifications.ts`
|
|
24
|
+
* can be deleted and the wake path can go back to `input.client`.
|
|
25
|
+
*/
|
|
26
|
+
import { createOpencodeClient } from "@opencode-ai/sdk";
|
|
27
|
+
export type LiveServerClient = ReturnType<typeof createOpencodeClient>;
|
|
28
|
+
/**
|
|
29
|
+
* Return a cached `createOpencodeClient` pointed at the live HTTP listener
|
|
30
|
+
* for the given `(serverUrl, directory)` pair. One client object is reused
|
|
31
|
+
* across many wakes for a given session.
|
|
32
|
+
*
|
|
33
|
+
* The `fetch` is bound to `globalThis.fetch` explicitly. Without this, the
|
|
34
|
+
* SDK would fall back to `globalThis.fetch` anyway in normal Node runtimes,
|
|
35
|
+
* but we set it on purpose so anyone reading this code (or grepping for the
|
|
36
|
+
* bug fix) can see that we intentionally chose the live HTTP transport.
|
|
37
|
+
*/
|
|
38
|
+
export declare function getLiveServerClient(serverUrl: string, directory: string): LiveServerClient;
|
|
39
|
+
/** Test helper — drop the cache between cases so each test starts clean. */
|
|
40
|
+
export declare function __resetLiveServerClientCacheForTests(): void;
|
|
41
|
+
/**
|
|
42
|
+
* True when the current runtime is Bun. OpenCode's TUI ships with Bun;
|
|
43
|
+
* the Electron Desktop app ships with Node. We use this to gate the
|
|
44
|
+
* `--port 0` nudge: Desktop is unaffected by anomalyco/opencode#28202
|
|
45
|
+
* because Node's webidl polyfill is complete, so undici v8 (and the live
|
|
46
|
+
* HTTP server) work without additional flags.
|
|
47
|
+
*/
|
|
48
|
+
export declare function isBunRuntime(): boolean;
|
|
49
|
+
/**
|
|
50
|
+
* Probe whether `serverUrl` accepts a connection within `timeoutMs`.
|
|
51
|
+
* Returns `true` for any HTTP response (including 4xx / 5xx) since the
|
|
52
|
+
* goal is to confirm the listener exists. Returns `false` on connection
|
|
53
|
+
* refused, DNS failure, timeout, or undefined URL.
|
|
54
|
+
*
|
|
55
|
+
* Used at plugin init under Bun to detect TUI sessions started without
|
|
56
|
+
* `opencode --port 0` — those bind an internal-only listener that 404s
|
|
57
|
+
* for `/session/...` endpoints and breaks AFT's wake path.
|
|
58
|
+
*/
|
|
59
|
+
export declare function probeServerReachable(serverUrl: string | undefined, timeoutMs?: number): Promise<boolean>;
|
|
60
|
+
//# sourceMappingURL=live-server-client.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"live-server-client.d.ts","sourceRoot":"","sources":["../../src/shared/live-server-client.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AAEH,OAAO,EAAE,oBAAoB,EAAE,MAAM,kBAAkB,CAAC;AAExD,MAAM,MAAM,gBAAgB,GAAG,UAAU,CAAC,OAAO,oBAAoB,CAAC,CAAC;AA6BvE;;;;;;;;;GASG;AACH,wBAAgB,mBAAmB,CAAC,SAAS,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,gBAAgB,CAY1F;AAED,4EAA4E;AAC5E,wBAAgB,oCAAoC,IAAI,IAAI,CAE3D;AAED;;;;;;GAMG;AACH,wBAAgB,YAAY,IAAI,OAAO,CAEtC;AAED;;;;;;;;;GASG;AACH,wBAAsB,oBAAoB,CACxC,SAAS,EAAE,MAAM,GAAG,SAAS,EAC7B,SAAS,SAAO,GACf,OAAO,CAAC,OAAO,CAAC,CAqBlB"}
|
|
@@ -1,18 +1,28 @@
|
|
|
1
1
|
export declare class AftRpcClient {
|
|
2
2
|
private port;
|
|
3
3
|
private token;
|
|
4
|
-
private
|
|
5
|
-
private
|
|
4
|
+
private portsDir;
|
|
5
|
+
private legacyPortFile;
|
|
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
|
|
10
|
-
|
|
11
|
-
|
|
9
|
+
private callOne;
|
|
10
|
+
/**
|
|
11
|
+
* Heuristic for "this response is the lazy-spawn placeholder, not the real
|
|
12
|
+
* data." We treat any `not_initialized` status as a placeholder so the
|
|
13
|
+
* client knows to try the next port (the warm one).
|
|
14
|
+
*/
|
|
15
|
+
private looksLikePlaceholder;
|
|
16
|
+
/** Check if any RPC server is reachable. */
|
|
12
17
|
isAvailable(): Promise<boolean>;
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
18
|
+
/**
|
|
19
|
+
* Discover all live RPC port files for this project. Tries the per-instance
|
|
20
|
+
* directory first (v0.28.2+), then falls back to the single legacy `port`
|
|
21
|
+
* file (older plugin versions in mixed deployments).
|
|
22
|
+
*/
|
|
23
|
+
private resolvePortInfos;
|
|
24
|
+
private readAllPortFiles;
|
|
25
|
+
private parsePortFile;
|
|
16
26
|
private healthCheck;
|
|
17
27
|
private fetchWithTimeout;
|
|
18
28
|
reset(): void;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"rpc-client.d.ts","sourceRoot":"","sources":["../../src/shared/rpc-client.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"rpc-client.d.ts","sourceRoot":"","sources":["../../src/shared/rpc-client.ts"],"names":[],"mappings":"AAWA,qBAAa,YAAY;IACvB,OAAO,CAAC,IAAI,CAAuB;IACnC,OAAO,CAAC,KAAK,CAAuB;IACpC,OAAO,CAAC,QAAQ,CAAS;IACzB,OAAO,CAAC,cAAc,CAAS;gBAEnB,UAAU,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM;IAKjD,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;YAoCC,OAAO;IAiBrB;;;;OAIG;IACH,OAAO,CAAC,oBAAoB;IAM5B,4CAA4C;IACtC,WAAW,IAAI,OAAO,CAAC,OAAO,CAAC;IASrC;;;;OAIG;YACW,gBAAgB;IAmB9B,OAAO,CAAC,gBAAgB;IAuBxB,OAAO,CAAC,aAAa;YAuBP,WAAW;YAWX,gBAAgB;IAU9B,KAAK,IAAI,IAAI;CAId"}
|
|
@@ -5,6 +5,9 @@ export declare class AftRpcServer {
|
|
|
5
5
|
private token;
|
|
6
6
|
private handlers;
|
|
7
7
|
private portFilePath;
|
|
8
|
+
private portsDir;
|
|
9
|
+
/** Unique per-instance ID — distinguishes our entry from duplicate plugin loads. */
|
|
10
|
+
private instanceId;
|
|
8
11
|
constructor(storageDir: string, directory: string);
|
|
9
12
|
/** Register an RPC method handler. */
|
|
10
13
|
handle(method: string, handler: RpcHandler): void;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"rpc-server.d.ts","sourceRoot":"","sources":["../../src/shared/rpc-server.ts"],"names":[],"mappings":"AAOA,KAAK,UAAU,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAAK,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC;AAExF,qBAAa,YAAY;IACvB,OAAO,CAAC,MAAM,CAAuB;IACrC,OAAO,CAAC,IAAI,CAAK;IACjB,OAAO,CAAC,KAAK,CAAuB;IACpC,OAAO,CAAC,QAAQ,CAAiC;IACjD,OAAO,CAAC,YAAY,CAAS;
|
|
1
|
+
{"version":3,"file":"rpc-server.d.ts","sourceRoot":"","sources":["../../src/shared/rpc-server.ts"],"names":[],"mappings":"AAOA,KAAK,UAAU,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAAK,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC;AAExF,qBAAa,YAAY;IACvB,OAAO,CAAC,MAAM,CAAuB;IACrC,OAAO,CAAC,IAAI,CAAK;IACjB,OAAO,CAAC,KAAK,CAAuB;IACpC,OAAO,CAAC,QAAQ,CAAiC;IACjD,OAAO,CAAC,YAAY,CAAS;IAC7B,OAAO,CAAC,QAAQ,CAAS;IACzB,oFAAoF;IACpF,OAAO,CAAC,UAAU,CAAS;gBAEf,UAAU,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM;IAMjD,sCAAsC;IACtC,MAAM,CAAC,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,UAAU,GAAG,IAAI;IAIjD,6DAA6D;IACvD,KAAK,IAAI,OAAO,CAAC,MAAM,CAAC;IA0C9B,8CAA8C;IAC9C,IAAI,IAAI,IAAI;IAaZ,OAAO,CAAC,QAAQ;CAoEjB"}
|
|
@@ -5,7 +5,25 @@
|
|
|
5
5
|
*/
|
|
6
6
|
export declare function projectHash(directory: string): string;
|
|
7
7
|
/**
|
|
8
|
-
*
|
|
8
|
+
* Legacy per-project RPC port file path (single file).
|
|
9
|
+
*
|
|
10
|
+
* Kept exported for backward-compatibility readers — when an older plugin
|
|
11
|
+
* instance is running alongside a newer one, the older one still writes
|
|
12
|
+
* to this path. New code prefers `rpcPortFileDir` (one file per instance)
|
|
13
|
+
* so that two plugin instances under `opencode --port 0` don't overwrite
|
|
14
|
+
* each other's port info. The client falls back to the legacy file if
|
|
15
|
+
* the new directory has no entries.
|
|
9
16
|
*/
|
|
10
17
|
export declare function rpcPortFilePath(storageDir: string, directory: string): string;
|
|
18
|
+
/**
|
|
19
|
+
* Per-project RPC port directory. Each plugin instance writes a file
|
|
20
|
+
* `<instance-id>.json` into this directory so the client can discover
|
|
21
|
+
* ALL active plugin instances (e.g. the two created by OpenCode TUI when
|
|
22
|
+
* launched with `--port 0`). The client tries each port and uses the
|
|
23
|
+
* first one whose bridge is warm.
|
|
24
|
+
*
|
|
25
|
+
* Replaces the single `port` file used pre-v0.28.2 (which suffered from
|
|
26
|
+
* last-write-wins racing under `--port 0`).
|
|
27
|
+
*/
|
|
28
|
+
export declare function rpcPortFileDir(storageDir: string, directory: string): string;
|
|
11
29
|
//# sourceMappingURL=rpc-utils.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"rpc-utils.d.ts","sourceRoot":"","sources":["../../src/shared/rpc-utils.ts"],"names":[],"mappings":"AAGA;;;;GAIG;AACH,wBAAgB,WAAW,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM,CAIrD;AAED
|
|
1
|
+
{"version":3,"file":"rpc-utils.d.ts","sourceRoot":"","sources":["../../src/shared/rpc-utils.ts"],"names":[],"mappings":"AAGA;;;;GAIG;AACH,wBAAgB,WAAW,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM,CAIrD;AAED;;;;;;;;;GASG;AACH,wBAAgB,eAAe,CAAC,UAAU,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,MAAM,CAG7E;AAED;;;;;;;;;GASG;AACH,wBAAgB,cAAc,CAAC,UAAU,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,MAAM,CAG5E"}
|
package/dist/tui.js
CHANGED
|
@@ -1,12 +1,11 @@
|
|
|
1
|
-
// @bun
|
|
2
1
|
// src/tui/index.tsx
|
|
3
2
|
import { createMemo as createMemo2, createSignal as createSignal2, onCleanup as onCleanup2 } from "solid-js";
|
|
4
3
|
// package.json
|
|
5
4
|
var package_default = {
|
|
6
5
|
name: "@cortexkit/aft-opencode",
|
|
7
|
-
version: "0.28.
|
|
6
|
+
version: "0.28.1",
|
|
8
7
|
type: "module",
|
|
9
|
-
description: "OpenCode plugin for Agent File Tools (AFT)
|
|
8
|
+
description: "OpenCode plugin for Agent File Tools (AFT) — tree-sitter and lsp powered code analysis",
|
|
10
9
|
main: "dist/index.js",
|
|
11
10
|
types: "dist/index.d.ts",
|
|
12
11
|
license: "MIT",
|
|
@@ -22,7 +21,7 @@ var package_default = {
|
|
|
22
21
|
"README.md"
|
|
23
22
|
],
|
|
24
23
|
scripts: {
|
|
25
|
-
build: "bun build src/index.ts --outdir dist --target
|
|
24
|
+
build: "bun build src/index.ts --outdir dist --target node --format esm --external @opencode-ai/plugin --external @opencode-ai/sdk && bun build src/tui/index.tsx --outfile dist/tui.js --target node --format esm --external @opencode-ai/plugin --external @opencode-ai/plugin/tui --external @opencode-ai/sdk --external @opentui/solid --external solid-js && tsc --emitDeclarationOnly",
|
|
26
25
|
typecheck: "tsc --noEmit",
|
|
27
26
|
pretest: "cd ../.. && cargo build",
|
|
28
27
|
test: "bun test",
|
|
@@ -33,7 +32,7 @@ var package_default = {
|
|
|
33
32
|
},
|
|
34
33
|
dependencies: {
|
|
35
34
|
"@clack/prompts": "^1.2.0",
|
|
36
|
-
"@cortexkit/aft-bridge": "0.28.
|
|
35
|
+
"@cortexkit/aft-bridge": "0.28.1",
|
|
37
36
|
"@opencode-ai/plugin": "^1.15.5",
|
|
38
37
|
"@opencode-ai/sdk": "^1.15.5",
|
|
39
38
|
"comment-json": "^4.6.2",
|
|
@@ -41,12 +40,12 @@ var package_default = {
|
|
|
41
40
|
zod: "^4.1.8"
|
|
42
41
|
},
|
|
43
42
|
optionalDependencies: {
|
|
44
|
-
"@cortexkit/aft-darwin-arm64": "0.28.
|
|
45
|
-
"@cortexkit/aft-darwin-x64": "0.28.
|
|
46
|
-
"@cortexkit/aft-linux-arm64": "0.28.
|
|
47
|
-
"@cortexkit/aft-linux-x64": "0.28.
|
|
48
|
-
"@cortexkit/aft-win32-arm64": "0.28.
|
|
49
|
-
"@cortexkit/aft-win32-x64": "0.28.
|
|
43
|
+
"@cortexkit/aft-darwin-arm64": "0.28.1",
|
|
44
|
+
"@cortexkit/aft-darwin-x64": "0.28.1",
|
|
45
|
+
"@cortexkit/aft-linux-arm64": "0.28.1",
|
|
46
|
+
"@cortexkit/aft-linux-x64": "0.28.1",
|
|
47
|
+
"@cortexkit/aft-win32-arm64": "0.28.1",
|
|
48
|
+
"@cortexkit/aft-win32-x64": "0.28.1"
|
|
50
49
|
},
|
|
51
50
|
devDependencies: {
|
|
52
51
|
"@types/node": "^22.0.0",
|
|
@@ -72,12 +71,13 @@ var package_default = {
|
|
|
72
71
|
};
|
|
73
72
|
|
|
74
73
|
// src/shared/rpc-client.ts
|
|
75
|
-
import { readFileSync } from "fs";
|
|
74
|
+
import { existsSync, readdirSync, readFileSync } from "node:fs";
|
|
75
|
+
import { join as join3 } from "node:path";
|
|
76
76
|
|
|
77
77
|
// src/logger.ts
|
|
78
|
-
import * as fs from "fs";
|
|
79
|
-
import * as os from "os";
|
|
80
|
-
import * as path from "path";
|
|
78
|
+
import * as fs from "node:fs";
|
|
79
|
+
import * as os from "node:os";
|
|
80
|
+
import * as path from "node:path";
|
|
81
81
|
var TAG = "[aft-plugin]";
|
|
82
82
|
var isTestEnv = process.env.BUN_TEST === "1" || false;
|
|
83
83
|
var logFile = path.join(os.tmpdir(), isTestEnv ? "aft-plugin-test.log" : "aft-plugin.log");
|
|
@@ -134,8 +134,8 @@ function warn(message, data) {
|
|
|
134
134
|
}
|
|
135
135
|
|
|
136
136
|
// src/shared/rpc-utils.ts
|
|
137
|
-
import { createHash } from "crypto";
|
|
138
|
-
import { join as join2 } from "path";
|
|
137
|
+
import { createHash } from "node:crypto";
|
|
138
|
+
import { join as join2 } from "node:path";
|
|
139
139
|
function projectHash(directory) {
|
|
140
140
|
const normalized = directory.replace(/\/+$/, "");
|
|
141
141
|
return createHash("sha256").update(normalized).digest("hex").slice(0, 16);
|
|
@@ -144,6 +144,10 @@ function rpcPortFilePath(storageDir, directory) {
|
|
|
144
144
|
const hash = projectHash(directory);
|
|
145
145
|
return join2(storageDir, "rpc", hash, "port");
|
|
146
146
|
}
|
|
147
|
+
function rpcPortFileDir(storageDir, directory) {
|
|
148
|
+
const hash = projectHash(directory);
|
|
149
|
+
return join2(storageDir, "rpc", hash, "ports");
|
|
150
|
+
}
|
|
147
151
|
|
|
148
152
|
// src/shared/rpc-client.ts
|
|
149
153
|
var MAX_RETRIES = 10;
|
|
@@ -153,95 +157,106 @@ var REQUEST_TIMEOUT_MS = 5000;
|
|
|
153
157
|
class AftRpcClient {
|
|
154
158
|
port = null;
|
|
155
159
|
token = null;
|
|
156
|
-
|
|
157
|
-
|
|
160
|
+
portsDir;
|
|
161
|
+
legacyPortFile;
|
|
158
162
|
constructor(storageDir, directory) {
|
|
159
|
-
this.
|
|
163
|
+
this.portsDir = rpcPortFileDir(storageDir, directory);
|
|
164
|
+
this.legacyPortFile = rpcPortFilePath(storageDir, directory);
|
|
160
165
|
}
|
|
161
166
|
async call(method, params = {}) {
|
|
162
|
-
const
|
|
163
|
-
if (
|
|
167
|
+
const infos = await this.resolvePortInfos();
|
|
168
|
+
if (infos.length === 0) {
|
|
164
169
|
throw new Error("AFT RPC server not available");
|
|
165
170
|
}
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
171
|
+
let placeholder = null;
|
|
172
|
+
let lastError = null;
|
|
173
|
+
for (const info of infos) {
|
|
174
|
+
try {
|
|
175
|
+
const result = await this.callOne(method, params, info);
|
|
176
|
+
if (this.looksLikePlaceholder(result)) {
|
|
177
|
+
placeholder = result;
|
|
178
|
+
continue;
|
|
179
|
+
}
|
|
180
|
+
this.port = info.port;
|
|
181
|
+
this.token = info.token;
|
|
182
|
+
return result;
|
|
183
|
+
} catch (err) {
|
|
184
|
+
lastError = err;
|
|
185
|
+
}
|
|
180
186
|
}
|
|
187
|
+
if (placeholder !== null)
|
|
188
|
+
return placeholder;
|
|
189
|
+
throw lastError instanceof Error ? lastError : new Error(String(lastError));
|
|
190
|
+
}
|
|
191
|
+
async callOne(method, params, info) {
|
|
192
|
+
const response = await this.fetchWithTimeout(`http://127.0.0.1:${info.port}/rpc/${method}`, {
|
|
193
|
+
method: "POST",
|
|
194
|
+
headers: { "Content-Type": "application/json" },
|
|
195
|
+
body: JSON.stringify({ ...params, token: info.token })
|
|
196
|
+
});
|
|
181
197
|
if (!response.ok) {
|
|
182
198
|
const text = await response.text();
|
|
183
|
-
if (response.status >= 500 && retryOnConnectionFailure) {
|
|
184
|
-
return this.retryAfterReset(method, params, `HTTP ${response.status}: ${text}`);
|
|
185
|
-
}
|
|
186
199
|
throw new Error(`RPC ${method} failed (${response.status}): ${text}`);
|
|
187
200
|
}
|
|
188
201
|
return await response.json();
|
|
189
202
|
}
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
if (!refreshed) {
|
|
196
|
-
throw new Error("AFT RPC server not available after port refresh");
|
|
197
|
-
}
|
|
198
|
-
return this.callResolved(method, params, refreshed, false);
|
|
203
|
+
looksLikePlaceholder(result) {
|
|
204
|
+
if (!result || typeof result !== "object")
|
|
205
|
+
return false;
|
|
206
|
+
const status = result.status;
|
|
207
|
+
return status === "not_initialized";
|
|
199
208
|
}
|
|
200
209
|
async isAvailable() {
|
|
201
210
|
try {
|
|
202
|
-
const
|
|
203
|
-
return
|
|
211
|
+
const infos = await this.resolvePortInfos();
|
|
212
|
+
return infos.length > 0;
|
|
204
213
|
} catch {
|
|
205
214
|
return false;
|
|
206
215
|
}
|
|
207
216
|
}
|
|
208
|
-
async
|
|
209
|
-
return (await this.resolvePortInfo())?.port ?? null;
|
|
210
|
-
}
|
|
211
|
-
async resolvePortInfo() {
|
|
212
|
-
if (this.port && this.healthChecked) {
|
|
213
|
-
return { port: this.port, token: this.token };
|
|
214
|
-
}
|
|
215
|
-
if (this.port) {
|
|
216
|
-
const alive = await this.healthCheck(this.port);
|
|
217
|
-
if (alive) {
|
|
218
|
-
this.healthChecked = true;
|
|
219
|
-
return { port: this.port, token: this.token };
|
|
220
|
-
}
|
|
221
|
-
this.port = null;
|
|
222
|
-
this.token = null;
|
|
223
|
-
this.healthChecked = false;
|
|
224
|
-
}
|
|
217
|
+
async resolvePortInfos() {
|
|
225
218
|
for (let attempt = 0;attempt < MAX_RETRIES; attempt++) {
|
|
226
|
-
const
|
|
227
|
-
if (
|
|
228
|
-
const alive =
|
|
229
|
-
|
|
230
|
-
this.
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
return info;
|
|
219
|
+
const infos = this.readAllPortFiles();
|
|
220
|
+
if (infos.length > 0) {
|
|
221
|
+
const alive = [];
|
|
222
|
+
for (const info of infos) {
|
|
223
|
+
if (await this.healthCheck(info.port)) {
|
|
224
|
+
alive.push(info);
|
|
225
|
+
}
|
|
234
226
|
}
|
|
227
|
+
if (alive.length > 0)
|
|
228
|
+
return alive;
|
|
235
229
|
}
|
|
236
230
|
if (attempt < MAX_RETRIES - 1) {
|
|
237
231
|
await new Promise((r) => setTimeout(r, RETRY_DELAY_MS));
|
|
238
232
|
}
|
|
239
233
|
}
|
|
240
|
-
return
|
|
234
|
+
return [];
|
|
235
|
+
}
|
|
236
|
+
readAllPortFiles() {
|
|
237
|
+
const collected = [];
|
|
238
|
+
if (existsSync(this.portsDir)) {
|
|
239
|
+
try {
|
|
240
|
+
const entries = readdirSync(this.portsDir);
|
|
241
|
+
for (const entry of entries) {
|
|
242
|
+
if (!entry.endsWith(".json"))
|
|
243
|
+
continue;
|
|
244
|
+
const info = this.parsePortFile(join3(this.portsDir, entry));
|
|
245
|
+
if (info)
|
|
246
|
+
collected.push(info);
|
|
247
|
+
}
|
|
248
|
+
} catch {}
|
|
249
|
+
}
|
|
250
|
+
if (collected.length === 0) {
|
|
251
|
+
const info = this.parsePortFile(this.legacyPortFile);
|
|
252
|
+
if (info)
|
|
253
|
+
collected.push(info);
|
|
254
|
+
}
|
|
255
|
+
return collected;
|
|
241
256
|
}
|
|
242
|
-
|
|
257
|
+
parsePortFile(path2) {
|
|
243
258
|
try {
|
|
244
|
-
const content = readFileSync(
|
|
259
|
+
const content = readFileSync(path2, "utf-8").trim();
|
|
245
260
|
let port;
|
|
246
261
|
let token;
|
|
247
262
|
if (content.startsWith("{")) {
|
|
@@ -283,7 +298,6 @@ class AftRpcClient {
|
|
|
283
298
|
reset() {
|
|
284
299
|
this.port = null;
|
|
285
300
|
this.token = null;
|
|
286
|
-
this.healthChecked = false;
|
|
287
301
|
}
|
|
288
302
|
}
|
|
289
303
|
|
|
@@ -409,7 +423,7 @@ var REFRESH_DEBOUNCE_MS = 200;
|
|
|
409
423
|
var POLL_INTERVAL_MS = 1500;
|
|
410
424
|
function formatBytes2(n) {
|
|
411
425
|
if (!Number.isFinite(n) || n <= 0)
|
|
412
|
-
return "
|
|
426
|
+
return "—";
|
|
413
427
|
if (n >= 1073741824)
|
|
414
428
|
return `${(n / 1073741824).toFixed(1)} GB`;
|
|
415
429
|
if (n >= 1048576)
|
|
@@ -420,7 +434,7 @@ function formatBytes2(n) {
|
|
|
420
434
|
}
|
|
421
435
|
function formatCount(n) {
|
|
422
436
|
if (n == null || !Number.isFinite(n))
|
|
423
|
-
return "
|
|
437
|
+
return "—";
|
|
424
438
|
if (n >= 1e6)
|
|
425
439
|
return `${(n / 1e6).toFixed(1)}M`;
|
|
426
440
|
if (n >= 1000)
|
|
@@ -680,7 +694,7 @@ var SidebarContent = (props) => {
|
|
|
680
694
|
children: /* @__PURE__ */ jsxDEV("text", {
|
|
681
695
|
fg: props.theme.warning,
|
|
682
696
|
children: [
|
|
683
|
-
"
|
|
697
|
+
"⚠ ",
|
|
684
698
|
degradedSummary()
|
|
685
699
|
]
|
|
686
700
|
}, undefined, true, undefined, this)
|
|
@@ -771,7 +785,7 @@ var SidebarContent = (props) => {
|
|
|
771
785
|
children: /* @__PURE__ */ jsxDEV("text", {
|
|
772
786
|
fg: props.theme.error,
|
|
773
787
|
children: [
|
|
774
|
-
"
|
|
788
|
+
"⚠ ",
|
|
775
789
|
s().semantic_index.error
|
|
776
790
|
]
|
|
777
791
|
}, undefined, true, undefined, this)
|
|
@@ -825,7 +839,7 @@ function getSessionId(api) {
|
|
|
825
839
|
var POLL_INTERVAL_MS2 = 1500;
|
|
826
840
|
function formatCountShort(value) {
|
|
827
841
|
if (value == null || !Number.isFinite(value))
|
|
828
|
-
return "
|
|
842
|
+
return "—";
|
|
829
843
|
if (value >= 1e6)
|
|
830
844
|
return `${(value / 1e6).toFixed(1)}M`;
|
|
831
845
|
if (value >= 1000)
|
|
@@ -917,7 +931,7 @@ var StatusDialog = (props) => {
|
|
|
917
931
|
/* @__PURE__ */ jsxDEV2("text", {
|
|
918
932
|
fg: t().accent,
|
|
919
933
|
children: /* @__PURE__ */ jsxDEV2("b", {
|
|
920
|
-
children: "
|
|
934
|
+
children: "⚡ AFT Status"
|
|
921
935
|
}, undefined, false, undefined, this)
|
|
922
936
|
}, undefined, false, undefined, this),
|
|
923
937
|
status()?.cache_role !== "not_initialized" && /* @__PURE__ */ jsxDEV2("text", {
|
|
@@ -1170,7 +1184,7 @@ var StatusDialog = (props) => {
|
|
|
1170
1184
|
children: /* @__PURE__ */ jsxDEV2("text", {
|
|
1171
1185
|
fg: t().error,
|
|
1172
1186
|
children: [
|
|
1173
|
-
"
|
|
1187
|
+
"⚠ ",
|
|
1174
1188
|
status().semantic_index.error
|
|
1175
1189
|
]
|
|
1176
1190
|
}, undefined, true, undefined, this)
|
|
@@ -1293,7 +1307,7 @@ async function showStartupNotifications(api) {
|
|
|
1293
1307
|
try {
|
|
1294
1308
|
const announcement = await client.call("get-announcement", {});
|
|
1295
1309
|
if (announcement.show && announcement.version && announcement.features?.length) {
|
|
1296
|
-
const featureText = announcement.features.map((f) => `
|
|
1310
|
+
const featureText = announcement.features.map((f) => ` • ${f}`).join(`
|
|
1297
1311
|
`);
|
|
1298
1312
|
const hasFooter = typeof announcement.footer === "string" && announcement.footer.trim().length > 0;
|
|
1299
1313
|
const message = hasFooter ? `What's new:
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cortexkit/aft-opencode",
|
|
3
|
-
"version": "0.28.
|
|
3
|
+
"version": "0.28.1",
|
|
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",
|
|
@@ -18,7 +18,7 @@
|
|
|
18
18
|
"README.md"
|
|
19
19
|
],
|
|
20
20
|
"scripts": {
|
|
21
|
-
"build": "bun build src/index.ts --outdir dist --target
|
|
21
|
+
"build": "bun build src/index.ts --outdir dist --target node --format esm --external @opencode-ai/plugin --external @opencode-ai/sdk && bun build src/tui/index.tsx --outfile dist/tui.js --target node --format esm --external @opencode-ai/plugin --external @opencode-ai/plugin/tui --external @opencode-ai/sdk --external @opentui/solid --external solid-js && tsc --emitDeclarationOnly",
|
|
22
22
|
"typecheck": "tsc --noEmit",
|
|
23
23
|
"pretest": "cd ../.. && cargo build",
|
|
24
24
|
"test": "bun test",
|
|
@@ -29,7 +29,7 @@
|
|
|
29
29
|
},
|
|
30
30
|
"dependencies": {
|
|
31
31
|
"@clack/prompts": "^1.2.0",
|
|
32
|
-
"@cortexkit/aft-bridge": "0.28.
|
|
32
|
+
"@cortexkit/aft-bridge": "0.28.1",
|
|
33
33
|
"@opencode-ai/plugin": "^1.15.5",
|
|
34
34
|
"@opencode-ai/sdk": "^1.15.5",
|
|
35
35
|
"comment-json": "^4.6.2",
|
|
@@ -37,12 +37,12 @@
|
|
|
37
37
|
"zod": "^4.1.8"
|
|
38
38
|
},
|
|
39
39
|
"optionalDependencies": {
|
|
40
|
-
"@cortexkit/aft-darwin-arm64": "0.28.
|
|
41
|
-
"@cortexkit/aft-darwin-x64": "0.28.
|
|
42
|
-
"@cortexkit/aft-linux-arm64": "0.28.
|
|
43
|
-
"@cortexkit/aft-linux-x64": "0.28.
|
|
44
|
-
"@cortexkit/aft-win32-arm64": "0.28.
|
|
45
|
-
"@cortexkit/aft-win32-x64": "0.28.
|
|
40
|
+
"@cortexkit/aft-darwin-arm64": "0.28.1",
|
|
41
|
+
"@cortexkit/aft-darwin-x64": "0.28.1",
|
|
42
|
+
"@cortexkit/aft-linux-arm64": "0.28.1",
|
|
43
|
+
"@cortexkit/aft-linux-x64": "0.28.1",
|
|
44
|
+
"@cortexkit/aft-win32-arm64": "0.28.1",
|
|
45
|
+
"@cortexkit/aft-win32-x64": "0.28.1"
|
|
46
46
|
},
|
|
47
47
|
"devDependencies": {
|
|
48
48
|
"@types/node": "^22.0.0",
|