@cortexkit/aft-opencode 0.29.0 → 0.30.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/bg-notifications.d.ts +90 -7
- package/dist/bg-notifications.d.ts.map +1 -1
- package/dist/config.d.ts +3 -0
- package/dist/config.d.ts.map +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4602 -209
- package/dist/shared/live-server-client.d.ts +30 -11
- package/dist/shared/live-server-client.d.ts.map +1 -1
- package/dist/shared/pty-cache.d.ts +18 -0
- package/dist/shared/pty-cache.d.ts.map +1 -0
- package/dist/tools/bash.d.ts.map +1 -1
- package/dist/tools/bash_watch.d.ts +24 -0
- package/dist/tools/bash_watch.d.ts.map +1 -0
- package/dist/tools/bash_write.d.ts +38 -0
- package/dist/tools/bash_write.d.ts.map +1 -0
- package/dist/tools/hoisted.d.ts +1 -1
- package/dist/tools/hoisted.d.ts.map +1 -1
- package/dist/tui.js +9 -8
- package/dist/workflow-hints.d.ts.map +1 -1
- package/package.json +9 -8
- package/src/shared/live-server-client.ts +56 -14
- package/src/shared/pty-cache.ts +113 -0
|
@@ -19,6 +19,16 @@
|
|
|
19
19
|
* UI uses, so the active session's `SessionRunState` is the one that
|
|
20
20
|
* resolves `ensureRunning` and overlapping turns coalesce correctly.
|
|
21
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. When the listener is unreachable
|
|
27
|
+
* the wake path silently uses the in-process `input.client.session.promptAsync`,
|
|
28
|
+
* which keeps wakes flowing (at the cost of the upstream duplicate-runner
|
|
29
|
+
* bug) instead of producing no notification at all or nagging the user
|
|
30
|
+
* to relaunch with a different flag.
|
|
31
|
+
*
|
|
22
32
|
* Tracked upstream as anomalyco/opencode#28202. When OpenCode fixes the
|
|
23
33
|
* runtime split, this helper and its single consumer in `bg-notifications.ts`
|
|
24
34
|
* can be deleted and the wake path can go back to `input.client`.
|
|
@@ -38,23 +48,32 @@ export type LiveServerClient = ReturnType<typeof createOpencodeClient>;
|
|
|
38
48
|
export declare function getLiveServerClient(serverUrl: string, directory: string): LiveServerClient;
|
|
39
49
|
/** Test helper — drop the cache between cases so each test starts clean. */
|
|
40
50
|
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
51
|
/**
|
|
50
52
|
* Probe whether `serverUrl` accepts a connection within `timeoutMs`.
|
|
51
53
|
* Returns `true` for any HTTP response (including 4xx / 5xx) since the
|
|
52
54
|
* goal is to confirm the listener exists. Returns `false` on connection
|
|
53
55
|
* refused, DNS failure, timeout, or undefined URL.
|
|
54
56
|
*
|
|
55
|
-
* Used at plugin init
|
|
56
|
-
*
|
|
57
|
-
*
|
|
57
|
+
* Used at plugin init to decide whether bg-notifications should use the
|
|
58
|
+
* live-server wake transport (workaround for anomalyco/opencode#28202)
|
|
59
|
+
* or fall back to the in-process `input.client.session.promptAsync`
|
|
60
|
+
* path. Plain TUI (no `--port 0`) binds an internal-only listener that
|
|
61
|
+
* 404s for `/session/...`, so this returns false there; OpenCode
|
|
62
|
+
* Desktop, `opencode run`, and `opencode --port 0` TUI return true.
|
|
58
63
|
*/
|
|
59
64
|
export declare function probeServerReachable(serverUrl: string | undefined, timeoutMs?: number): Promise<boolean>;
|
|
65
|
+
/**
|
|
66
|
+
* Record the probe result. Idempotent; if you record twice, the latest
|
|
67
|
+
* value wins. The wake path reads through `useLiveServerWake()`.
|
|
68
|
+
*/
|
|
69
|
+
export declare function setLiveServerWakeAvailable(available: boolean): void;
|
|
70
|
+
/**
|
|
71
|
+
* Read the cached probe decision. `true` means the wake path should use
|
|
72
|
+
* `getLiveServerClient(serverUrl, directory)` and POST through the live
|
|
73
|
+
* HTTP listener. `false` means fall back to the in-process client passed
|
|
74
|
+
* via plugin context (`input.client`).
|
|
75
|
+
*/
|
|
76
|
+
export declare function useLiveServerWake(): boolean;
|
|
77
|
+
/** Test helper — reset the decision cache between cases. */
|
|
78
|
+
export declare function __resetLiveServerWakeForTests(): void;
|
|
60
79
|
//# sourceMappingURL=live-server-client.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"live-server-client.d.ts","sourceRoot":"","sources":["../../src/shared/live-server-client.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"live-server-client.d.ts","sourceRoot":"","sources":["../../src/shared/live-server-client.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAkCG;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;;;;;;;;;;;;GAYG;AACH,wBAAsB,oBAAoB,CACxC,SAAS,EAAE,MAAM,GAAG,SAAS,EAC7B,SAAS,SAAO,GACf,OAAO,CAAC,OAAO,CAAC,CAqBlB;AAmBD;;;GAGG;AACH,wBAAgB,0BAA0B,CAAC,SAAS,EAAE,OAAO,GAAG,IAAI,CAEnE;AAED;;;;;GAKG;AACH,wBAAgB,iBAAiB,IAAI,OAAO,CAE3C;AAED,4DAA4D;AAC5D,wBAAgB,6BAA6B,IAAI,IAAI,CAEpD"}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import * as fs from "node:fs/promises";
|
|
2
|
+
import { Terminal } from "@xterm/headless";
|
|
3
|
+
export interface PtyTerminalState {
|
|
4
|
+
terminal: Terminal;
|
|
5
|
+
fileHandle: fs.FileHandle;
|
|
6
|
+
offset: number;
|
|
7
|
+
rows: number;
|
|
8
|
+
cols: number;
|
|
9
|
+
lastAccessMs: number;
|
|
10
|
+
}
|
|
11
|
+
export declare function getOrCreatePtyTerminal(key: string, outputPath: string, rows?: number, cols?: number): Promise<PtyTerminalState>;
|
|
12
|
+
export declare function readPtyBytes(state: PtyTerminalState): Promise<Buffer>;
|
|
13
|
+
export declare function disposePtyTerminal(key: string): Promise<void>;
|
|
14
|
+
export declare function disposeAllPtyTerminals(): Promise<void>;
|
|
15
|
+
export declare function renderScreen(state: PtyTerminalState, rows?: number, cols?: number): string;
|
|
16
|
+
export declare function __resetPtyCacheForTests(): void;
|
|
17
|
+
export declare function __ptyCacheSizeForTests(): number;
|
|
18
|
+
//# sourceMappingURL=pty-cache.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"pty-cache.d.ts","sourceRoot":"","sources":["../../src/shared/pty-cache.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,kBAAkB,CAAC;AACvC,OAAO,EAAE,QAAQ,EAAE,MAAM,iBAAiB,CAAC;AAM3C,MAAM,WAAW,gBAAgB;IAC/B,QAAQ,EAAE,QAAQ,CAAC;IACnB,UAAU,EAAE,EAAE,CAAC,UAAU,CAAC;IAC1B,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,YAAY,EAAE,MAAM,CAAC;CACtB;AAID,wBAAsB,sBAAsB,CAC1C,GAAG,EAAE,MAAM,EACX,UAAU,EAAE,MAAM,EAClB,IAAI,SAAe,EACnB,IAAI,SAAe,GAClB,OAAO,CAAC,gBAAgB,CAAC,CAuB3B;AAED,wBAAsB,YAAY,CAAC,KAAK,EAAE,gBAAgB,GAAG,OAAO,CAAC,MAAM,CAAC,CAa3E;AAED,wBAAsB,kBAAkB,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAMnE;AAED,wBAAsB,sBAAsB,IAAI,OAAO,CAAC,IAAI,CAAC,CAE5D;AAED,wBAAgB,YAAY,CAC1B,KAAK,EAAE,gBAAgB,EACvB,IAAI,SAAa,EACjB,IAAI,SAAa,GAChB,MAAM,CAiBR;AAED,wBAAgB,uBAAuB,IAAI,IAAI,CAM9C;AAED,wBAAgB,sBAAsB,IAAI,MAAM,CAE/C"}
|
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":"AACA,OAAO,KAAK,EAAe,cAAc,EAAE,MAAM,qBAAqB,CAAC;
|
|
1
|
+
{"version":3,"file":"bash.d.ts","sourceRoot":"","sources":["../../src/tools/bash.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAe,cAAc,EAAE,MAAM,qBAAqB,CAAC;AAavE,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AA0EjD,wBAAgB,cAAc,CAAC,GAAG,EAAE,aAAa,GAAG,cAAc,CAmRjE;AAED,wBAAgB,oBAAoB,CAAC,GAAG,EAAE,aAAa,GAAG,cAAc,CAyBvE;AAED,wBAAgB,kBAAkB,CAAC,GAAG,EAAE,aAAa,GAAG,cAAc,CAuBrE"}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import type { ToolContext, ToolDefinition } from "@opencode-ai/plugin";
|
|
2
|
+
import type { PluginContext } from "../types.js";
|
|
3
|
+
export type BashWaitPattern = {
|
|
4
|
+
kind: "substring";
|
|
5
|
+
value: string;
|
|
6
|
+
} | {
|
|
7
|
+
kind: "regex";
|
|
8
|
+
value: RegExp;
|
|
9
|
+
source: string;
|
|
10
|
+
};
|
|
11
|
+
export type BashStatusWaited = {
|
|
12
|
+
reason: "matched" | "exited" | "timeout";
|
|
13
|
+
elapsed_ms: number;
|
|
14
|
+
match?: string;
|
|
15
|
+
match_offset?: number;
|
|
16
|
+
};
|
|
17
|
+
type BashStatusWithWait = Record<string, unknown> & {
|
|
18
|
+
waited?: BashStatusWaited;
|
|
19
|
+
};
|
|
20
|
+
export declare function createBashWatchTool(ctx: PluginContext): ToolDefinition;
|
|
21
|
+
export declare function waitForBashStatus(ctx: PluginContext, runtime: ToolContext, taskId: string, outputMode: string | undefined, waitFor: BashWaitPattern | undefined, effectiveWaitMs: number): Promise<BashStatusWithWait>;
|
|
22
|
+
export declare function parseWaitPattern(value: unknown): BashWaitPattern | undefined;
|
|
23
|
+
export {};
|
|
24
|
+
//# sourceMappingURL=bash_watch.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"bash_watch.d.ts","sourceRoot":"","sources":["../../src/tools/bash_watch.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,WAAW,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC;AAavE,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AASjD,MAAM,MAAM,eAAe,GACvB;IAAE,IAAI,EAAE,WAAW,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,GACpC;IAAE,IAAI,EAAE,OAAO,CAAC;IAAC,KAAK,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,CAAC;AACrD,MAAM,MAAM,gBAAgB,GAAG;IAC7B,MAAM,EAAE,SAAS,GAAG,QAAQ,GAAG,SAAS,CAAC;IACzC,UAAU,EAAE,MAAM,CAAC;IACnB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB,CAAC;AACF,KAAK,kBAAkB,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG;IAAE,MAAM,CAAC,EAAE,gBAAgB,CAAA;CAAE,CAAC;AAElF,wBAAgB,mBAAmB,CAAC,GAAG,EAAE,aAAa,GAAG,cAAc,CA4FtE;AAiDD,wBAAsB,iBAAiB,CACrC,GAAG,EAAE,aAAa,EAClB,OAAO,EAAE,WAAW,EACpB,MAAM,EAAE,MAAM,EACd,UAAU,EAAE,MAAM,GAAG,SAAS,EAC9B,OAAO,EAAE,eAAe,GAAG,SAAS,EACpC,eAAe,EAAE,MAAM,GACtB,OAAO,CAAC,kBAAkB,CAAC,CAmD7B;AAsCD,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,OAAO,GAAG,eAAe,GAAG,SAAS,CAK5E"}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import type { ToolDefinition } from "@opencode-ai/plugin";
|
|
2
|
+
import type { PluginContext } from "../types.js";
|
|
3
|
+
/**
|
|
4
|
+
* Write bytes to a running PTY background task.
|
|
5
|
+
*
|
|
6
|
+
* Two input shapes:
|
|
7
|
+
*
|
|
8
|
+
* - **String** — verbatim bytes written to the PTY. Use this for plain text
|
|
9
|
+
* such as REPL commands (`"print(1)\n"`) or when the agent specifically
|
|
10
|
+
* wants the literal characters `\u001b` (e.g. writing source code).
|
|
11
|
+
*
|
|
12
|
+
* - **Sequence array** — mix plain strings (text) with `{ key: "<name>" }`
|
|
13
|
+
* objects (named control keys). Items concatenate into one atomic write
|
|
14
|
+
* so the PTY sees the whole sequence as one input chunk. The agent never
|
|
15
|
+
* has to JSON-encode escape characters.
|
|
16
|
+
*
|
|
17
|
+
* Allowed key names: `enter`, `return`, `tab`, `space`, `backspace`,
|
|
18
|
+
* `esc`, `escape`, `up`, `down`, `left`, `right`, `home`, `end`,
|
|
19
|
+
* `page-up`, `page-down`, `delete`, `insert`, `f1`..`f12`,
|
|
20
|
+
* `ctrl-a`..`ctrl-z`. Names are case-insensitive.
|
|
21
|
+
*
|
|
22
|
+
* The vim "type text, exit insert, save and quit" idiom:
|
|
23
|
+
*
|
|
24
|
+
* ```ts
|
|
25
|
+
* bash_write({ taskId, input: [
|
|
26
|
+
* "iHello",
|
|
27
|
+
* { key: "esc" },
|
|
28
|
+
* ":wq",
|
|
29
|
+
* { key: "enter" },
|
|
30
|
+
* ]})
|
|
31
|
+
* ```
|
|
32
|
+
*
|
|
33
|
+
* Rust enforces a 1 MiB maximum on the EXPANDED byte stream. Agents should
|
|
34
|
+
* check `bash_status` first and only call `bash_write` when the task reports
|
|
35
|
+
* `mode: "pty"`.
|
|
36
|
+
*/
|
|
37
|
+
export declare function createBashWriteTool(ctx: PluginContext): ToolDefinition;
|
|
38
|
+
//# sourceMappingURL=bash_write.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"bash_write.d.ts","sourceRoot":"","sources":["../../src/tools/bash_write.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC;AAE1D,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAKjD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAiCG;AACH,wBAAgB,mBAAmB,CAAC,GAAG,EAAE,aAAa,GAAG,cAAc,CA6CtE"}
|
package/dist/tools/hoisted.d.ts
CHANGED
|
@@ -20,7 +20,7 @@ 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 is opt-in: `bash`, `bash_status`, and `bash_kill` are
|
|
23
|
+
* Bash hoisting is opt-in: `bash`, `bash_status`, `bash_write`, and `bash_kill` are
|
|
24
24
|
* registered together when at least one `experimental.bash.*` flag is
|
|
25
25
|
* enabled (rewrite, compress, or background). When all flags are off,
|
|
26
26
|
* opencode's native bash stays in place — users without bash experimentals
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"hoisted.d.ts","sourceRoot":"","sources":["../../src/tools/hoisted.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAIH,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC;AAK1D,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;
|
|
1
|
+
{"version":3,"file":"hoisted.d.ts","sourceRoot":"","sources":["../../src/tools/hoisted.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAIH,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC;AAK1D,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAyEjD,wEAAwE;AACxE,eAAO,MAAM,wBAAwB,GAAI,IAAI,MAAM,EAAE,QAAQ,MAAM,EAAE,OAAO,MAAM,KAAG,MAChD,CAAC;AAqRtC;;GAEG;AACH,wBAAgB,cAAc,CAAC,GAAG,EAAE,aAAa,GAAG,cAAc,CA+JjE;AAmmCD;;;;;;;;;;;;;;;;GAgBG;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,CA8FnF"}
|
package/dist/tui.js
CHANGED
|
@@ -3,7 +3,7 @@ import { createMemo as createMemo2, createSignal as createSignal2, onCleanup as
|
|
|
3
3
|
// package.json
|
|
4
4
|
var package_default = {
|
|
5
5
|
name: "@cortexkit/aft-opencode",
|
|
6
|
-
version: "0.
|
|
6
|
+
version: "0.30.0",
|
|
7
7
|
type: "module",
|
|
8
8
|
description: "OpenCode plugin for Agent File Tools (AFT) — tree-sitter and lsp powered code analysis",
|
|
9
9
|
main: "dist/index.js",
|
|
@@ -32,20 +32,21 @@ var package_default = {
|
|
|
32
32
|
},
|
|
33
33
|
dependencies: {
|
|
34
34
|
"@clack/prompts": "^1.2.0",
|
|
35
|
-
"@cortexkit/aft-bridge": "0.
|
|
35
|
+
"@cortexkit/aft-bridge": "0.30.0",
|
|
36
36
|
"@opencode-ai/plugin": "^1.15.5",
|
|
37
37
|
"@opencode-ai/sdk": "^1.15.5",
|
|
38
|
+
"@xterm/headless": "^5.5.0",
|
|
38
39
|
"comment-json": "^4.6.2",
|
|
39
40
|
undici: "^7.25.0",
|
|
40
41
|
zod: "^4.1.8"
|
|
41
42
|
},
|
|
42
43
|
optionalDependencies: {
|
|
43
|
-
"@cortexkit/aft-darwin-arm64": "0.
|
|
44
|
-
"@cortexkit/aft-darwin-x64": "0.
|
|
45
|
-
"@cortexkit/aft-linux-arm64": "0.
|
|
46
|
-
"@cortexkit/aft-linux-x64": "0.
|
|
47
|
-
"@cortexkit/aft-win32-arm64": "0.
|
|
48
|
-
"@cortexkit/aft-win32-x64": "0.
|
|
44
|
+
"@cortexkit/aft-darwin-arm64": "0.30.0",
|
|
45
|
+
"@cortexkit/aft-darwin-x64": "0.30.0",
|
|
46
|
+
"@cortexkit/aft-linux-arm64": "0.30.0",
|
|
47
|
+
"@cortexkit/aft-linux-x64": "0.30.0",
|
|
48
|
+
"@cortexkit/aft-win32-arm64": "0.30.0",
|
|
49
|
+
"@cortexkit/aft-win32-x64": "0.30.0"
|
|
49
50
|
},
|
|
50
51
|
devDependencies: {
|
|
51
52
|
"@types/node": "^22.0.0",
|
|
@@ -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,wEAAwE;IACxE,qBAAqB,EAAE,OAAO,CAAC;IAC/B,4DAA4D;IAC5D,aAAa,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC;CAC5B;AAID;;;;GAIG;AACH,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,iBAAiB,GAAG,MAAM,GAAG,IAAI,
|
|
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,wEAAwE;IACxE,qBAAqB,EAAE,OAAO,CAAC;IAC/B,4DAA4D;IAC5D,aAAa,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC;CAC5B;AAID;;;;GAIG;AACH,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,iBAAiB,GAAG,MAAM,GAAG,IAAI,CAuEzE;AAED;;;;;;;GAOG;AACH,wBAAgB,oBAAoB,CAAC,MAAM,EAAE,SAAS,EAAE,aAAa,EAAE,GAAG,CAAC,MAAM,CAAC,GAAG,MAAM,GAAG,IAAI,CAQjG"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cortexkit/aft-opencode",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.30.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",
|
|
@@ -29,20 +29,21 @@
|
|
|
29
29
|
},
|
|
30
30
|
"dependencies": {
|
|
31
31
|
"@clack/prompts": "^1.2.0",
|
|
32
|
-
"@cortexkit/aft-bridge": "0.
|
|
32
|
+
"@cortexkit/aft-bridge": "0.30.0",
|
|
33
33
|
"@opencode-ai/plugin": "^1.15.5",
|
|
34
34
|
"@opencode-ai/sdk": "^1.15.5",
|
|
35
|
+
"@xterm/headless": "^5.5.0",
|
|
35
36
|
"comment-json": "^4.6.2",
|
|
36
37
|
"undici": "^7.25.0",
|
|
37
38
|
"zod": "^4.1.8"
|
|
38
39
|
},
|
|
39
40
|
"optionalDependencies": {
|
|
40
|
-
"@cortexkit/aft-darwin-arm64": "0.
|
|
41
|
-
"@cortexkit/aft-darwin-x64": "0.
|
|
42
|
-
"@cortexkit/aft-linux-arm64": "0.
|
|
43
|
-
"@cortexkit/aft-linux-x64": "0.
|
|
44
|
-
"@cortexkit/aft-win32-arm64": "0.
|
|
45
|
-
"@cortexkit/aft-win32-x64": "0.
|
|
41
|
+
"@cortexkit/aft-darwin-arm64": "0.30.0",
|
|
42
|
+
"@cortexkit/aft-darwin-x64": "0.30.0",
|
|
43
|
+
"@cortexkit/aft-linux-arm64": "0.30.0",
|
|
44
|
+
"@cortexkit/aft-linux-x64": "0.30.0",
|
|
45
|
+
"@cortexkit/aft-win32-arm64": "0.30.0",
|
|
46
|
+
"@cortexkit/aft-win32-x64": "0.30.0"
|
|
46
47
|
},
|
|
47
48
|
"devDependencies": {
|
|
48
49
|
"@types/node": "^22.0.0",
|
|
@@ -19,6 +19,16 @@
|
|
|
19
19
|
* UI uses, so the active session's `SessionRunState` is the one that
|
|
20
20
|
* resolves `ensureRunning` and overlapping turns coalesce correctly.
|
|
21
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. When the listener is unreachable
|
|
27
|
+
* the wake path silently uses the in-process `input.client.session.promptAsync`,
|
|
28
|
+
* which keeps wakes flowing (at the cost of the upstream duplicate-runner
|
|
29
|
+
* bug) instead of producing no notification at all or nagging the user
|
|
30
|
+
* to relaunch with a different flag.
|
|
31
|
+
*
|
|
22
32
|
* Tracked upstream as anomalyco/opencode#28202. When OpenCode fixes the
|
|
23
33
|
* runtime split, this helper and its single consumer in `bg-notifications.ts`
|
|
24
34
|
* can be deleted and the wake path can go back to `input.client`.
|
|
@@ -84,26 +94,18 @@ export function __resetLiveServerClientCacheForTests(): void {
|
|
|
84
94
|
clientCache.clear();
|
|
85
95
|
}
|
|
86
96
|
|
|
87
|
-
/**
|
|
88
|
-
* True when the current runtime is Bun. OpenCode's TUI ships with Bun;
|
|
89
|
-
* the Electron Desktop app ships with Node. We use this to gate the
|
|
90
|
-
* `--port 0` nudge: Desktop is unaffected by anomalyco/opencode#28202
|
|
91
|
-
* because Node's webidl polyfill is complete, so undici v8 (and the live
|
|
92
|
-
* HTTP server) work without additional flags.
|
|
93
|
-
*/
|
|
94
|
-
export function isBunRuntime(): boolean {
|
|
95
|
-
return typeof (globalThis as { Bun?: unknown }).Bun !== "undefined";
|
|
96
|
-
}
|
|
97
|
-
|
|
98
97
|
/**
|
|
99
98
|
* Probe whether `serverUrl` accepts a connection within `timeoutMs`.
|
|
100
99
|
* Returns `true` for any HTTP response (including 4xx / 5xx) since the
|
|
101
100
|
* goal is to confirm the listener exists. Returns `false` on connection
|
|
102
101
|
* refused, DNS failure, timeout, or undefined URL.
|
|
103
102
|
*
|
|
104
|
-
* Used at plugin init
|
|
105
|
-
*
|
|
106
|
-
*
|
|
103
|
+
* Used at plugin init to decide whether bg-notifications should use the
|
|
104
|
+
* live-server wake transport (workaround for anomalyco/opencode#28202)
|
|
105
|
+
* or fall back to the in-process `input.client.session.promptAsync`
|
|
106
|
+
* path. Plain TUI (no `--port 0`) binds an internal-only listener that
|
|
107
|
+
* 404s for `/session/...`, so this returns false there; OpenCode
|
|
108
|
+
* Desktop, `opencode run`, and `opencode --port 0` TUI return true.
|
|
107
109
|
*/
|
|
108
110
|
export async function probeServerReachable(
|
|
109
111
|
serverUrl: string | undefined,
|
|
@@ -130,3 +132,43 @@ export async function probeServerReachable(
|
|
|
130
132
|
clearTimeout(timer);
|
|
131
133
|
}
|
|
132
134
|
}
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* Per-plugin-process decision: should bg-notifications use the live-server
|
|
138
|
+
* wake transport (workaround for anomalyco/opencode#28202), or fall back
|
|
139
|
+
* to the in-process `input.client.session.promptAsync` path?
|
|
140
|
+
*
|
|
141
|
+
* Set once at plugin init from the result of `probeServerReachable()`.
|
|
142
|
+
* Defaults to `false` if no decision has been recorded yet — that's the
|
|
143
|
+
* safe direction because `input.client.session.promptAsync` is always
|
|
144
|
+
* available (it's part of the plugin contract), whereas the live-server
|
|
145
|
+
* path needs both a probe-confirmed listener and the workaround code
|
|
146
|
+
* itself to be live.
|
|
147
|
+
*
|
|
148
|
+
* Read at wake time (not cached on a closure) so background probes that
|
|
149
|
+
* complete after plugin init still take effect on the next wake.
|
|
150
|
+
*/
|
|
151
|
+
let liveServerWakeAvailable = false;
|
|
152
|
+
|
|
153
|
+
/**
|
|
154
|
+
* Record the probe result. Idempotent; if you record twice, the latest
|
|
155
|
+
* value wins. The wake path reads through `useLiveServerWake()`.
|
|
156
|
+
*/
|
|
157
|
+
export function setLiveServerWakeAvailable(available: boolean): void {
|
|
158
|
+
liveServerWakeAvailable = available;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
/**
|
|
162
|
+
* Read the cached probe decision. `true` means the wake path should use
|
|
163
|
+
* `getLiveServerClient(serverUrl, directory)` and POST through the live
|
|
164
|
+
* HTTP listener. `false` means fall back to the in-process client passed
|
|
165
|
+
* via plugin context (`input.client`).
|
|
166
|
+
*/
|
|
167
|
+
export function useLiveServerWake(): boolean {
|
|
168
|
+
return liveServerWakeAvailable;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
/** Test helper — reset the decision cache between cases. */
|
|
172
|
+
export function __resetLiveServerWakeForTests(): void {
|
|
173
|
+
liveServerWakeAvailable = false;
|
|
174
|
+
}
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
import * as fs from "node:fs/promises";
|
|
2
|
+
import { Terminal } from "@xterm/headless";
|
|
3
|
+
|
|
4
|
+
const DEFAULT_COLS = 80;
|
|
5
|
+
const DEFAULT_ROWS = 24;
|
|
6
|
+
const READ_CHUNK_SIZE = 64 * 1024;
|
|
7
|
+
|
|
8
|
+
export interface PtyTerminalState {
|
|
9
|
+
terminal: Terminal;
|
|
10
|
+
fileHandle: fs.FileHandle;
|
|
11
|
+
offset: number;
|
|
12
|
+
rows: number;
|
|
13
|
+
cols: number;
|
|
14
|
+
lastAccessMs: number;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
const terminals = new Map<string, PtyTerminalState>();
|
|
18
|
+
|
|
19
|
+
export async function getOrCreatePtyTerminal(
|
|
20
|
+
key: string,
|
|
21
|
+
outputPath: string,
|
|
22
|
+
rows = DEFAULT_ROWS,
|
|
23
|
+
cols = DEFAULT_COLS,
|
|
24
|
+
): Promise<PtyTerminalState> {
|
|
25
|
+
const existing = terminals.get(key);
|
|
26
|
+
if (existing) {
|
|
27
|
+
existing.lastAccessMs = Date.now();
|
|
28
|
+
if (existing.rows === rows && existing.cols === cols) {
|
|
29
|
+
return existing;
|
|
30
|
+
}
|
|
31
|
+
terminals.delete(key);
|
|
32
|
+
existing.terminal.dispose();
|
|
33
|
+
await existing.fileHandle.close().catch(() => undefined);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
const fileHandle = await fs.open(outputPath, "r");
|
|
37
|
+
const state: PtyTerminalState = {
|
|
38
|
+
terminal: new Terminal({ cols, rows, allowProposedApi: true }),
|
|
39
|
+
fileHandle,
|
|
40
|
+
offset: 0,
|
|
41
|
+
rows,
|
|
42
|
+
cols,
|
|
43
|
+
lastAccessMs: Date.now(),
|
|
44
|
+
};
|
|
45
|
+
terminals.set(key, state);
|
|
46
|
+
return state;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export async function readPtyBytes(state: PtyTerminalState): Promise<Buffer> {
|
|
50
|
+
const chunks: Buffer[] = [];
|
|
51
|
+
while (true) {
|
|
52
|
+
const buffer = Buffer.allocUnsafe(READ_CHUNK_SIZE);
|
|
53
|
+
const { bytesRead } = await state.fileHandle.read(buffer, 0, buffer.length, state.offset);
|
|
54
|
+
if (bytesRead === 0) break;
|
|
55
|
+
const chunk = buffer.subarray(0, bytesRead);
|
|
56
|
+
chunks.push(Buffer.from(chunk));
|
|
57
|
+
await writeTerminal(state.terminal, chunk);
|
|
58
|
+
state.offset += bytesRead;
|
|
59
|
+
}
|
|
60
|
+
state.lastAccessMs = Date.now();
|
|
61
|
+
return Buffer.concat(chunks);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export async function disposePtyTerminal(key: string): Promise<void> {
|
|
65
|
+
const state = terminals.get(key);
|
|
66
|
+
if (!state) return;
|
|
67
|
+
terminals.delete(key);
|
|
68
|
+
state.terminal.dispose();
|
|
69
|
+
await state.fileHandle.close().catch(() => undefined);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
export async function disposeAllPtyTerminals(): Promise<void> {
|
|
73
|
+
await Promise.all([...terminals.keys()].map((key) => disposePtyTerminal(key)));
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
export function renderScreen(
|
|
77
|
+
state: PtyTerminalState,
|
|
78
|
+
rows = state.rows,
|
|
79
|
+
cols = state.cols,
|
|
80
|
+
): string {
|
|
81
|
+
const active = state.terminal.buffer.active;
|
|
82
|
+
const lines: string[] = [];
|
|
83
|
+
for (let y = 0; y < rows; y++) {
|
|
84
|
+
const line = active.getLine(active.baseY + y);
|
|
85
|
+
if (!line) {
|
|
86
|
+
lines.push("");
|
|
87
|
+
continue;
|
|
88
|
+
}
|
|
89
|
+
let text = "";
|
|
90
|
+
for (let x = 0; x < cols; x++) {
|
|
91
|
+
text += line.getCell(x)?.getChars() || " ";
|
|
92
|
+
}
|
|
93
|
+
lines.push(text.trimEnd());
|
|
94
|
+
}
|
|
95
|
+
while (lines.length > 0 && lines[lines.length - 1] === "") lines.pop();
|
|
96
|
+
return lines.join("\n");
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
export function __resetPtyCacheForTests(): void {
|
|
100
|
+
for (const state of terminals.values()) {
|
|
101
|
+
state.terminal.dispose();
|
|
102
|
+
void state.fileHandle.close().catch(() => undefined);
|
|
103
|
+
}
|
|
104
|
+
terminals.clear();
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
export function __ptyCacheSizeForTests(): number {
|
|
108
|
+
return terminals.size;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
function writeTerminal(terminal: Terminal, data: Uint8Array): Promise<void> {
|
|
112
|
+
return new Promise((resolve) => terminal.write(data, resolve));
|
|
113
|
+
}
|