@cortexkit/aft-opencode 0.28.2 → 0.29.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 +22 -7
- package/dist/bg-notifications.d.ts.map +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +60 -47
- package/dist/shared/bash-hints.d.ts +24 -0
- package/dist/shared/bash-hints.d.ts.map +1 -0
- package/dist/shared/live-server-client.d.ts +30 -11
- package/dist/shared/live-server-client.d.ts.map +1 -1
- package/dist/tui.js +8 -8
- package/package.json +8 -8
- package/src/shared/bash-hints.ts +45 -0
- package/src/shared/live-server-client.ts +56 -14
|
@@ -46,13 +46,28 @@ interface DrainContext {
|
|
|
46
46
|
directory: string;
|
|
47
47
|
sessionID: string;
|
|
48
48
|
/**
|
|
49
|
-
*
|
|
50
|
-
*
|
|
51
|
-
* live listener
|
|
52
|
-
*
|
|
53
|
-
* the
|
|
54
|
-
*
|
|
55
|
-
*
|
|
49
|
+
* Plugin-provided OpenCode SDK client (`input.client`). The wake path
|
|
50
|
+
* uses this as a fallback when `useLiveServerWake()` is false — i.e.
|
|
51
|
+
* the live HTTP listener was unreachable when probed at plugin init,
|
|
52
|
+
* so `getLiveServerClient(...)` cannot be built. Falling back here
|
|
53
|
+
* accepts the upstream `promptAsync` runner-split bug
|
|
54
|
+
* (anomalyco/opencode#28202; duplicate "stop" messages) in exchange
|
|
55
|
+
* for wakes still arriving at all in plain-TUI sessions.
|
|
56
|
+
*
|
|
57
|
+
* Typed `unknown` because the real `@opencode-ai/sdk` `OpencodeClient`
|
|
58
|
+
* has a narrower, generated `promptAsync` signature than the loose
|
|
59
|
+
* structural `OpenCodeClient` shape used by the live-server factory
|
|
60
|
+
* and test stubs. The wake closure asserts to `OpenCodeClient` after
|
|
61
|
+
* deciding which transport to use.
|
|
62
|
+
*/
|
|
63
|
+
client?: unknown;
|
|
64
|
+
/**
|
|
65
|
+
* Live OpenCode HTTP listener URL (from `input.serverUrl`). When the
|
|
66
|
+
* listener was reachable at startup, the wake path builds a separate
|
|
67
|
+
* `createOpencodeClient` from this URL so requests hit the same Effect
|
|
68
|
+
* memoMap as the live UI — works around the runner-split bug
|
|
69
|
+
* (anomalyco/opencode#28202). When the listener was unreachable, the
|
|
70
|
+
* wake path falls back to `client` above; this URL is unused.
|
|
56
71
|
*/
|
|
57
72
|
serverUrl?: string;
|
|
58
73
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"bg-notifications.d.ts","sourceRoot":"","sources":["../src/bg-notifications.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AAWhD,MAAM,WAAW,YAAY;IAC3B,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,EAAE,MAAM,CAAC;IACf,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,OAAO,EAAE,MAAM,CAAC;IAChB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,2EAA2E;IAC3E,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,qEAAqE;IACrE,gBAAgB,CAAC,EAAE,OAAO,CAAC;IAG3B,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B,cAAc,CAAC,EAAE,OAAO,CAAC;CAC1B;AAED,MAAM,WAAW,qBAAqB;IACpC,OAAO,EAAE,MAAM,CAAC;IAChB,UAAU,EAAE,MAAM,CAAC;IACnB,OAAO,EAAE,MAAM,CAAC;IAChB,UAAU,EAAE,MAAM,CAAC;CACpB;AAED,KAAK,cAAc,GAAG;IACpB,kBAAkB,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC;IAChC,kBAAkB,EAAE,YAAY,EAAE,CAAC;IACnC,kBAAkB,EAAE,qBAAqB,EAAE,CAAC;IAC5C,aAAa,EAAE,MAAM,CAAC,OAAO,GAAG,IAAI,CAAC;IACrC,iBAAiB,EAAE,MAAM,GAAG,IAAI,CAAC;IACjC,eAAe,EAAE,MAAM,GAAG,IAAI,CAAC;IAC/B,wBAAwB,EAAE,MAAM,CAAC;IACjC,YAAY,EAAE,MAAM,GAAG,IAAI,CAAC;IAC5B,iBAAiB,EAAE,MAAM,CAAC;IAC1B,eAAe,EAAE,OAAO,CAAC;IACzB,oBAAoB,EAAE,OAAO,CAAC;IAC9B,kBAAkB,EAAE,KAAK,CAAC;QAAE,UAAU,EAAE,YAAY,CAAC;QAAC,UAAU,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IAC5E,UAAU,EAAE,MAAM,CAAC;CACpB,CAAC;AAEF,eAAO,MAAM,eAAe,EAAE,GAAG,CAAC,MAAM,EAAE,cAAc,CAAa,CAAC;AAGtE,eAAO,MAAM,4BAA4B,QAAiB,CAAC;AAS3D,UAAU,YAAY;IACpB,GAAG,EAAE,aAAa,CAAC;IACnB,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;IAClB
|
|
1
|
+
{"version":3,"file":"bg-notifications.d.ts","sourceRoot":"","sources":["../src/bg-notifications.ts"],"names":[],"mappings":"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"}
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAeA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,qBAAqB,CAAC;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAeA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,qBAAqB,CAAC;AAqKlD;;;;;;;;;;;;;;;;;;GAkBG;AAQH,QAAA,MAAM,MAAM,EAAE,MAA6D,CAAC;AAqyB5E,eAAe,MAAM,CAAC"}
|
package/dist/index.js
CHANGED
|
@@ -31001,6 +31001,7 @@ import { chmodSync as chmodSync2, copyFileSync, existsSync as existsSync2, mkdir
|
|
|
31001
31001
|
import { createRequire as createRequire2 } from "node:module";
|
|
31002
31002
|
import { homedir as homedir3 } from "node:os";
|
|
31003
31003
|
import { join as join3 } from "node:path";
|
|
31004
|
+
var ensureBinaryForResolver = ensureBinary;
|
|
31004
31005
|
function readBinaryVersion(binaryPath) {
|
|
31005
31006
|
try {
|
|
31006
31007
|
const result = spawnSync(binaryPath, ["--version"], {
|
|
@@ -31141,7 +31142,7 @@ async function findBinary(expectedVersion) {
|
|
|
31141
31142
|
return syncResult;
|
|
31142
31143
|
}
|
|
31143
31144
|
log("Binary not found locally, attempting auto-download...");
|
|
31144
|
-
const downloaded = await
|
|
31145
|
+
const downloaded = await ensureBinaryForResolver(expectedVersion);
|
|
31145
31146
|
if (downloaded)
|
|
31146
31147
|
return downloaded;
|
|
31147
31148
|
throw new Error([
|
|
@@ -31165,6 +31166,7 @@ async function findBinary(expectedVersion) {
|
|
|
31165
31166
|
}
|
|
31166
31167
|
|
|
31167
31168
|
// ../aft-bridge/dist/migration.js
|
|
31169
|
+
var spawnSyncForMigration = spawnSync2;
|
|
31168
31170
|
var TARGET_MARKER = ".migrated_from_legacy";
|
|
31169
31171
|
var DEFAULT_TIMEOUT_MS = 30 * 60 * 1000;
|
|
31170
31172
|
function dataHome() {
|
|
@@ -31236,7 +31238,7 @@ async function ensureStorageMigrated(opts) {
|
|
|
31236
31238
|
|
|
31237
31239
|
`);
|
|
31238
31240
|
} catch {}
|
|
31239
|
-
const result =
|
|
31241
|
+
const result = spawnSyncForMigration(binaryPath, [
|
|
31240
31242
|
"migrate-storage",
|
|
31241
31243
|
"--from",
|
|
31242
31244
|
legacyRoot,
|
|
@@ -32692,9 +32694,6 @@ function getLiveServerClient(serverUrl, directory) {
|
|
|
32692
32694
|
clientCache.set(key, client);
|
|
32693
32695
|
return client;
|
|
32694
32696
|
}
|
|
32695
|
-
function isBunRuntime() {
|
|
32696
|
-
return typeof globalThis.Bun !== "undefined";
|
|
32697
|
-
}
|
|
32698
32697
|
async function probeServerReachable(serverUrl, timeoutMs = 1500) {
|
|
32699
32698
|
if (!serverUrl)
|
|
32700
32699
|
return false;
|
|
@@ -32713,6 +32712,13 @@ async function probeServerReachable(serverUrl, timeoutMs = 1500) {
|
|
|
32713
32712
|
clearTimeout(timer);
|
|
32714
32713
|
}
|
|
32715
32714
|
}
|
|
32715
|
+
var liveServerWakeAvailable = false;
|
|
32716
|
+
function setLiveServerWakeAvailable(available) {
|
|
32717
|
+
liveServerWakeAvailable = available;
|
|
32718
|
+
}
|
|
32719
|
+
function useLiveServerWake() {
|
|
32720
|
+
return liveServerWakeAvailable;
|
|
32721
|
+
}
|
|
32716
32722
|
|
|
32717
32723
|
// src/bg-notifications.ts
|
|
32718
32724
|
function hashReminder(text) {
|
|
@@ -32819,18 +32825,26 @@ async function triggerWakeIfPending(drainContext, skipDrain) {
|
|
|
32819
32825
|
if (state.pendingCompletions.length === 0 && state.pendingLongRunning.length === 0)
|
|
32820
32826
|
return;
|
|
32821
32827
|
scheduleWake(state, async (reminder, deliveredCompletions) => {
|
|
32822
|
-
|
|
32823
|
-
|
|
32824
|
-
|
|
32825
|
-
|
|
32826
|
-
|
|
32827
|
-
|
|
32828
|
-
|
|
32829
|
-
|
|
32828
|
+
let client;
|
|
32829
|
+
let clientPath;
|
|
32830
|
+
if (useLiveServerWake() && drainContext.serverUrl) {
|
|
32831
|
+
client = getLiveServerClient(drainContext.serverUrl, drainContext.directory);
|
|
32832
|
+
clientPath = "live-server";
|
|
32833
|
+
} else {
|
|
32834
|
+
if (!drainContext.client) {
|
|
32835
|
+
sessionWarn(drainContext.sessionID, `${LOG_PREFIX} wake client unavailable`, {
|
|
32836
|
+
event: "bash_completion_wake_client_unavailable",
|
|
32837
|
+
task_ids: deliveredCompletions.map((c) => c.task_id),
|
|
32838
|
+
directory: drainContext.directory,
|
|
32839
|
+
attempt: state.wakeRetryAttempts + 1
|
|
32840
|
+
});
|
|
32841
|
+
throw new Error("no wake transport available: live-server unreachable and input.client absent");
|
|
32842
|
+
}
|
|
32843
|
+
client = drainContext.client;
|
|
32844
|
+
clientPath = "in-process-fallback";
|
|
32830
32845
|
}
|
|
32831
|
-
const client = getLiveServerClient(drainContext.serverUrl, drainContext.directory);
|
|
32832
32846
|
if (typeof client.session?.promptAsync !== "function") {
|
|
32833
|
-
throw new Error(
|
|
32847
|
+
throw new Error(`wake client.session.promptAsync is unavailable (path=${clientPath})`);
|
|
32834
32848
|
}
|
|
32835
32849
|
const promptContext = await resolvePromptContext(client, drainContext.sessionID);
|
|
32836
32850
|
const body = {
|
|
@@ -32856,7 +32870,7 @@ async function triggerWakeIfPending(drainContext, skipDrain) {
|
|
|
32856
32870
|
directory: drainContext.directory,
|
|
32857
32871
|
reminder_sha256: hashReminder(reminder),
|
|
32858
32872
|
reminder_chars: reminder.length,
|
|
32859
|
-
wake_client_path:
|
|
32873
|
+
wake_client_path: clientPath,
|
|
32860
32874
|
prompt_context: promptContext ? {
|
|
32861
32875
|
agent: promptContext.agent,
|
|
32862
32876
|
model: promptContext.model ? {
|
|
@@ -49958,6 +49972,28 @@ async function cleanupWarnings(opts) {
|
|
|
49958
49972
|
}
|
|
49959
49973
|
}
|
|
49960
49974
|
|
|
49975
|
+
// src/shared/bash-hints.ts
|
|
49976
|
+
var CONFLICT_HINT = `
|
|
49977
|
+
|
|
49978
|
+
[Hint] Use aft_conflicts to see all conflict regions across files in a single call.`;
|
|
49979
|
+
var GREP_HINT = `
|
|
49980
|
+
|
|
49981
|
+
[Hint] Use the grep tool instead of bash for faster indexed search.`;
|
|
49982
|
+
function maybeAppendConflictsHint(output) {
|
|
49983
|
+
if (!output.includes("Automatic merge failed; fix conflicts"))
|
|
49984
|
+
return output;
|
|
49985
|
+
if (!/^CONFLICT \(|^error: could not apply /m.test(output))
|
|
49986
|
+
return output;
|
|
49987
|
+
return output + CONFLICT_HINT;
|
|
49988
|
+
}
|
|
49989
|
+
function maybeAppendGrepHint(output) {
|
|
49990
|
+
const firstLine = output.slice(0, 300).split(`
|
|
49991
|
+
`)[0] ?? "";
|
|
49992
|
+
if (!/\b(rg|grep)\s/.test(firstLine))
|
|
49993
|
+
return output;
|
|
49994
|
+
return output + GREP_HINT;
|
|
49995
|
+
}
|
|
49996
|
+
|
|
49961
49997
|
// src/shared/rpc-server.ts
|
|
49962
49998
|
import { randomBytes as randomBytes2 } from "node:crypto";
|
|
49963
49999
|
import { mkdirSync as mkdirSync11, renameSync as renameSync7, unlinkSync as unlinkSync7, writeFileSync as writeFileSync10 } from "node:fs";
|
|
@@ -53990,18 +54026,12 @@ ${lines}
|
|
|
53990
54026
|
config: aftConfig,
|
|
53991
54027
|
storageDir: configOverrides.storage_dir
|
|
53992
54028
|
};
|
|
53993
|
-
|
|
53994
|
-
|
|
53995
|
-
|
|
53996
|
-
|
|
53997
|
-
|
|
53998
|
-
|
|
53999
|
-
}
|
|
54000
|
-
}).catch(() => {
|
|
54001
|
-
tuiPortZeroNudgeNeeded = true;
|
|
54002
|
-
});
|
|
54003
|
-
}
|
|
54004
|
-
const tuiNudgeSentSessions = new Set;
|
|
54029
|
+
probeServerReachable(input.serverUrl?.toString()).then((reachable) => {
|
|
54030
|
+
setLiveServerWakeAvailable(reachable);
|
|
54031
|
+
log2(reachable ? "Live OpenCode HTTP listener reachable; bg-notifications wake path = live-server (anomalyco/opencode#28202 workaround active)." : "Live OpenCode HTTP listener unreachable; bg-notifications wake path = in-process-fallback. Wakes will still arrive but the upstream duplicate-runner bug (anomalyco/opencode#28202) is not worked around. Launch with `opencode --port 0` in TUI mode to activate the workaround.");
|
|
54032
|
+
}).catch(() => {
|
|
54033
|
+
setLiveServerWakeAvailable(false);
|
|
54034
|
+
});
|
|
54005
54035
|
if (onnxRuntimePromise) {
|
|
54006
54036
|
onnxRuntimePromise.then((ortDylibDir) => {
|
|
54007
54037
|
if (ortDylibDir) {
|
|
@@ -54215,13 +54245,6 @@ Install: ${getManualInstallHint()}`).catch(() => {});
|
|
|
54215
54245
|
"chat.message": async (messageInput) => {
|
|
54216
54246
|
const sid = messageInput.sessionID ?? messageInput.sessionId ?? messageInput.id;
|
|
54217
54247
|
warmSessionDirectory(input.client, sid, input.directory);
|
|
54218
|
-
if (tuiPortZeroNudgeNeeded && typeof sid === "string" && sid.length > 0) {
|
|
54219
|
-
if (!tuiNudgeSentSessions.has(sid)) {
|
|
54220
|
-
tuiNudgeSentSessions.add(sid);
|
|
54221
|
-
const nudge = "Due to a bug in opencode (https://github.com/anomalyco/opencode/issues/28202), background bash notifications are now using a workaround which needs opencode to be run via `opencode --port 0` in TUI mode. Opencode Desktop doesn't need special treatment.";
|
|
54222
|
-
sendIgnoredMessage2(input.client, sid, nudge).catch(() => {});
|
|
54223
|
-
}
|
|
54224
|
-
}
|
|
54225
54248
|
},
|
|
54226
54249
|
"command.execute.before": async (commandInput, _output) => {
|
|
54227
54250
|
if (isTuiMode2() || commandInput.command !== STATUS_COMMAND) {
|
|
@@ -54254,19 +54277,9 @@ Install: ${getManualInstallHint()}`).catch(() => {});
|
|
|
54254
54277
|
if (stored.metadata)
|
|
54255
54278
|
output.metadata = { ...output.metadata, ...stored.metadata };
|
|
54256
54279
|
}
|
|
54257
|
-
if (toolInput.tool === "bash" && output.output?.includes("Automatic merge failed; fix conflicts")) {
|
|
54258
|
-
output.output += `
|
|
54259
|
-
|
|
54260
|
-
[Hint] Use aft_conflicts to see all conflict regions across files in a single call.`;
|
|
54261
|
-
}
|
|
54262
54280
|
if (toolInput.tool === "bash" && output.output) {
|
|
54263
|
-
|
|
54264
|
-
|
|
54265
|
-
if (/\b(rg|grep)\s/.test(firstLine)) {
|
|
54266
|
-
output.output += `
|
|
54267
|
-
|
|
54268
|
-
[Hint] Use the grep tool instead of bash for faster indexed search.`;
|
|
54269
|
-
}
|
|
54281
|
+
output.output = maybeAppendConflictsHint(output.output);
|
|
54282
|
+
output.output = maybeAppendGrepHint(output.output);
|
|
54270
54283
|
}
|
|
54271
54284
|
const sessionDir = getSessionDirectoryCached(toolInput.sessionID) ?? input.directory;
|
|
54272
54285
|
await appendInTurnBgCompletions({ ctx, directory: sessionDir, sessionID: toolInput.sessionID }, output);
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Append the `aft_conflicts` hint when the output indicates a real git merge
|
|
3
|
+
* or rebase produced conflicts.
|
|
4
|
+
*
|
|
5
|
+
* Gated on BOTH:
|
|
6
|
+
* - the "Automatic merge failed; fix conflicts" marker, AND
|
|
7
|
+
* - a git-conflict signal (`CONFLICT (...)` line or `error: could not apply`)
|
|
8
|
+
*
|
|
9
|
+
* Both conditions are required because `aft_conflicts` calls `git ls-files -u`,
|
|
10
|
+
* which fails with "not a git repository" outside a git working tree. The
|
|
11
|
+
* marker string can legitimately appear in docs, READMEs, test fixtures, and
|
|
12
|
+
* grep output, so we cannot key off it alone — a false-positive hint sends
|
|
13
|
+
* agents into a confusing error.
|
|
14
|
+
*/
|
|
15
|
+
export declare function maybeAppendConflictsHint(output: string): string;
|
|
16
|
+
/**
|
|
17
|
+
* Append the grep-tool hint when the bash command was a grep/rg invocation.
|
|
18
|
+
*
|
|
19
|
+
* Detected by examining the FIRST LINE of output (which is the echoed command
|
|
20
|
+
* line on most shells in foreground mode). We cap the slice at 300 chars so
|
|
21
|
+
* a single huge first line doesn't slow this hook.
|
|
22
|
+
*/
|
|
23
|
+
export declare function maybeAppendGrepHint(output: string): string;
|
|
24
|
+
//# sourceMappingURL=bash-hints.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"bash-hints.d.ts","sourceRoot":"","sources":["../../src/shared/bash-hints.ts"],"names":[],"mappings":"AAWA;;;;;;;;;;;;;GAaG;AACH,wBAAgB,wBAAwB,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAM/D;AAED;;;;;;GAMG;AACH,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAI1D"}
|
|
@@ -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"}
|
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.29.1",
|
|
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,7 +32,7 @@ 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.29.1",
|
|
36
36
|
"@opencode-ai/plugin": "^1.15.5",
|
|
37
37
|
"@opencode-ai/sdk": "^1.15.5",
|
|
38
38
|
"comment-json": "^4.6.2",
|
|
@@ -40,12 +40,12 @@ var package_default = {
|
|
|
40
40
|
zod: "^4.1.8"
|
|
41
41
|
},
|
|
42
42
|
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.
|
|
43
|
+
"@cortexkit/aft-darwin-arm64": "0.29.1",
|
|
44
|
+
"@cortexkit/aft-darwin-x64": "0.29.1",
|
|
45
|
+
"@cortexkit/aft-linux-arm64": "0.29.1",
|
|
46
|
+
"@cortexkit/aft-linux-x64": "0.29.1",
|
|
47
|
+
"@cortexkit/aft-win32-arm64": "0.29.1",
|
|
48
|
+
"@cortexkit/aft-win32-x64": "0.29.1"
|
|
49
49
|
},
|
|
50
50
|
devDependencies: {
|
|
51
51
|
"@types/node": "^22.0.0",
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cortexkit/aft-opencode",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.29.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",
|
|
@@ -29,7 +29,7 @@
|
|
|
29
29
|
},
|
|
30
30
|
"dependencies": {
|
|
31
31
|
"@clack/prompts": "^1.2.0",
|
|
32
|
-
"@cortexkit/aft-bridge": "0.
|
|
32
|
+
"@cortexkit/aft-bridge": "0.29.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.
|
|
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.
|
|
40
|
+
"@cortexkit/aft-darwin-arm64": "0.29.1",
|
|
41
|
+
"@cortexkit/aft-darwin-x64": "0.29.1",
|
|
42
|
+
"@cortexkit/aft-linux-arm64": "0.29.1",
|
|
43
|
+
"@cortexkit/aft-linux-x64": "0.29.1",
|
|
44
|
+
"@cortexkit/aft-win32-arm64": "0.29.1",
|
|
45
|
+
"@cortexkit/aft-win32-x64": "0.29.1"
|
|
46
46
|
},
|
|
47
47
|
"devDependencies": {
|
|
48
48
|
"@types/node": "^22.0.0",
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
// Pure helpers for the bash-output hint nudges fired from `tool.execute.after`.
|
|
2
|
+
//
|
|
3
|
+
// These run on every bash result and append agent-visible "[Hint] ..." lines
|
|
4
|
+
// that steer toward AFT tools. The functions return the new output string
|
|
5
|
+
// (or the original string when no hint should fire) so the hook can compose
|
|
6
|
+
// them without managing state.
|
|
7
|
+
|
|
8
|
+
const CONFLICT_HINT =
|
|
9
|
+
"\n\n[Hint] Use aft_conflicts to see all conflict regions across files in a single call.";
|
|
10
|
+
const GREP_HINT = "\n\n[Hint] Use the grep tool instead of bash for faster indexed search.";
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Append the `aft_conflicts` hint when the output indicates a real git merge
|
|
14
|
+
* or rebase produced conflicts.
|
|
15
|
+
*
|
|
16
|
+
* Gated on BOTH:
|
|
17
|
+
* - the "Automatic merge failed; fix conflicts" marker, AND
|
|
18
|
+
* - a git-conflict signal (`CONFLICT (...)` line or `error: could not apply`)
|
|
19
|
+
*
|
|
20
|
+
* Both conditions are required because `aft_conflicts` calls `git ls-files -u`,
|
|
21
|
+
* which fails with "not a git repository" outside a git working tree. The
|
|
22
|
+
* marker string can legitimately appear in docs, READMEs, test fixtures, and
|
|
23
|
+
* grep output, so we cannot key off it alone — a false-positive hint sends
|
|
24
|
+
* agents into a confusing error.
|
|
25
|
+
*/
|
|
26
|
+
export function maybeAppendConflictsHint(output: string): string {
|
|
27
|
+
if (!output.includes("Automatic merge failed; fix conflicts")) return output;
|
|
28
|
+
// git merge prints "CONFLICT (content|file|...): ..." per file.
|
|
29
|
+
// git rebase / git am print "error: could not apply <sha>" per failed pick.
|
|
30
|
+
if (!/^CONFLICT \(|^error: could not apply /m.test(output)) return output;
|
|
31
|
+
return output + CONFLICT_HINT;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Append the grep-tool hint when the bash command was a grep/rg invocation.
|
|
36
|
+
*
|
|
37
|
+
* Detected by examining the FIRST LINE of output (which is the echoed command
|
|
38
|
+
* line on most shells in foreground mode). We cap the slice at 300 chars so
|
|
39
|
+
* a single huge first line doesn't slow this hook.
|
|
40
|
+
*/
|
|
41
|
+
export function maybeAppendGrepHint(output: string): string {
|
|
42
|
+
const firstLine = output.slice(0, 300).split("\n")[0] ?? "";
|
|
43
|
+
if (!/\b(rg|grep)\s/.test(firstLine)) return output;
|
|
44
|
+
return output + GREP_HINT;
|
|
45
|
+
}
|
|
@@ -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
|
+
}
|