@cortexkit/aft-opencode 0.34.0 → 0.35.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/config.d.ts +8 -6
- package/dist/config.d.ts.map +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +125 -34
- package/dist/shared/bash-hints.d.ts +1 -23
- package/dist/shared/bash-hints.d.ts.map +1 -1
- package/dist/status-bar-inject.d.ts +11 -0
- package/dist/status-bar-inject.d.ts.map +1 -0
- package/dist/tui.js +88 -5
- package/dist/workflow-hints.d.ts.map +1 -1
- package/package.json +8 -8
- package/src/shared/bash-hints.ts +4 -45
- package/src/tui/index.tsx +15 -1
- package/src/tui/sidebar.tsx +106 -4
package/dist/config.d.ts
CHANGED
|
@@ -7,8 +7,8 @@ export declare const ConfigureWarningsDeliveryEnum: z.ZodEnum<{
|
|
|
7
7
|
}>;
|
|
8
8
|
export type ConfigureWarningsDelivery = z.infer<typeof ConfigureWarningsDeliveryEnum>;
|
|
9
9
|
export declare const LspServerSchema: z.ZodObject<{
|
|
10
|
-
extensions: z.ZodArray<z.ZodString
|
|
11
|
-
binary: z.ZodString
|
|
10
|
+
extensions: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
11
|
+
binary: z.ZodOptional<z.ZodString>;
|
|
12
12
|
args: z.ZodDefault<z.ZodOptional<z.ZodArray<z.ZodString>>>;
|
|
13
13
|
root_markers: z.ZodDefault<z.ZodOptional<z.ZodArray<z.ZodString>>>;
|
|
14
14
|
disabled: z.ZodDefault<z.ZodOptional<z.ZodBoolean>>;
|
|
@@ -101,8 +101,8 @@ export declare const AftConfigSchema: z.ZodObject<{
|
|
|
101
101
|
}, z.core.$strip>>;
|
|
102
102
|
lsp: z.ZodOptional<z.ZodObject<{
|
|
103
103
|
servers: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodObject<{
|
|
104
|
-
extensions: z.ZodArray<z.ZodString
|
|
105
|
-
binary: z.ZodString
|
|
104
|
+
extensions: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
105
|
+
binary: z.ZodOptional<z.ZodString>;
|
|
106
106
|
args: z.ZodDefault<z.ZodOptional<z.ZodArray<z.ZodString>>>;
|
|
107
107
|
root_markers: z.ZodDefault<z.ZodOptional<z.ZodArray<z.ZodString>>>;
|
|
108
108
|
disabled: z.ZodDefault<z.ZodOptional<z.ZodBoolean>>;
|
|
@@ -141,8 +141,10 @@ export type AftConfig = z.infer<typeof AftConfigSchema>;
|
|
|
141
141
|
export type LspServerConfig = z.infer<typeof LspServerSchema>;
|
|
142
142
|
export interface ConfigureLspServer {
|
|
143
143
|
id: string;
|
|
144
|
-
extensions
|
|
145
|
-
|
|
144
|
+
/** Omitted when overriding a built-in server to inherit its extensions. */
|
|
145
|
+
extensions?: string[];
|
|
146
|
+
/** Omitted when overriding a built-in server to inherit its binary. */
|
|
147
|
+
binary?: string;
|
|
146
148
|
args: string[];
|
|
147
149
|
root_markers: string[];
|
|
148
150
|
disabled: boolean;
|
package/dist/config.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAIA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAgCxB,oGAAoG;AACpG,eAAO,MAAM,6BAA6B;;;;EAAmC,CAAC;AAC9E,MAAM,MAAM,yBAAyB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,6BAA6B,CAAC,CAAC;
|
|
1
|
+
{"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAIA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAgCxB,oGAAoG;AACpG,eAAO,MAAM,6BAA6B;;;;EAAmC,CAAC;AAC9E,MAAM,MAAM,yBAAyB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,6BAA6B,CAAC,CAAC;AA4CtF,eAAO,MAAM,eAAe;;;;;;;;;iBAE1B,CAAC;AAsHH,eAAO,MAAM,eAAe;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;kBAiGjB,CAAC;AAEZ,MAAM,MAAM,SAAS,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,eAAe,CAAC,CAAC;AAExD,MAAM,MAAM,eAAe,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,eAAe,CAAC,CAAC;AAE9D,MAAM,WAAW,kBAAkB;IACjC,EAAE,EAAE,MAAM,CAAC;IACX,2EAA2E;IAC3E,UAAU,CAAC,EAAE,MAAM,EAAE,CAAC;IACtB,uEAAuE;IACvE,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,IAAI,EAAE,MAAM,EAAE,CAAC;IACf,YAAY,EAAE,MAAM,EAAE,CAAC;IACvB,QAAQ,EAAE,OAAO,CAAC;IAClB,GAAG,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC7B,sBAAsB,CAAC,EAAE,OAAO,CAAC;CAClC;AAED,MAAM,WAAW,qBAAqB;IACpC,mBAAmB,CAAC,EAAE,OAAO,CAAC;IAC9B,WAAW,CAAC,EAAE,kBAAkB,EAAE,CAAC;IACnC,YAAY,CAAC,EAAE,MAAM,EAAE,CAAC;CACzB;AAED,MAAM,WAAW,8BAA8B;IAC7C,yBAAyB,CAAC,EAAE,OAAO,CAAC;IACpC,0BAA0B,CAAC,EAAE,OAAO,CAAC;IACrC,4BAA4B,CAAC,EAAE,OAAO,CAAC;IACvC,kCAAkC,CAAC,EAAE,OAAO,CAAC;IAC7C,sCAAsC,CAAC,EAAE,MAAM,CAAC;IAChD,mBAAmB,CAAC,EAAE,OAAO,CAAC;CAC/B;AAMD,wBAAgB,4BAA4B,CAAC,MAAM,EAAE,SAAS,GAAG,qBAAqB,CAuDrF;AAED;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAgB,mCAAmC,CAAC,MAAM,EAAE,SAAS,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CA6B9F;AAED;;;;;;;;;;GAUG;AACH,MAAM,WAAW,kBAAkB;IACjC,OAAO,EAAE,OAAO,CAAC;IACjB,OAAO,EAAE,OAAO,CAAC;IACjB,QAAQ,EAAE,OAAO,CAAC;IAClB,UAAU,EAAE,OAAO,CAAC;IACpB,iEAAiE;IACjE,mBAAmB,EAAE,OAAO,CAAC;IAC7B,6BAA6B,CAAC,EAAE,OAAO,CAAC;IACxC,iCAAiC,CAAC,EAAE,MAAM,CAAC;IAC3C;;;OAGG;IACH,yBAAyB,EAAE,MAAM,CAAC;CACnC;AAED,iEAAiE;AACjE,eAAO,MAAM,iCAAiC,OAAQ,CAAC;AACvD,4EAA4E;AAC5E,eAAO,MAAM,6BAA6B,OAAQ,CAAC;AAEnD;;;;;;;;;;;;;;;;;;;GAmBG;AACH,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,SAAS,GAAG,kBAAkB,CA0FvE;AAED,wBAAgB,qCAAqC,CACnD,MAAM,EAAE,SAAS,GAChB,8BAA8B,CAsBhC;AAED,KAAK,MAAM,GAAG;IACZ,GAAG,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,CAAC;IAC/B,IAAI,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,CAAC;CACjC,CAAC;AAoMF,wBAAgB,oBAAoB,CAClC,UAAU,EAAE,MAAM,EAClB,MAAM,GAAE,MAAsB,GAC7B;IAAE,QAAQ,EAAE,OAAO,CAAC;IAAC,OAAO,EAAE,MAAM,EAAE,CAAA;CAAE,CAwD1C;AAoZD;;;;;;;;;GASG;AACH,wBAAgB,aAAa,CAAC,gBAAgB,EAAE,MAAM,GAAG,SAAS,CAiDjE"}
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAcA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,qBAAqB,CAAC;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAcA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,qBAAqB,CAAC;AA6MlD;;;;;;;;;;;;;;;;;GAiBG;AAQH,QAAA,MAAM,MAAM,EAAE,MAA6D,CAAC;AA80B5E,eAAe,MAAM,CAAC"}
|
package/dist/index.js
CHANGED
|
@@ -11593,11 +11593,81 @@ function error(message, meta) {
|
|
|
11593
11593
|
console.error(`[aft-bridge] ERROR: ${message}`);
|
|
11594
11594
|
}
|
|
11595
11595
|
}
|
|
11596
|
+
// ../aft-bridge/dist/bash-hints.js
|
|
11597
|
+
var CONFLICT_HINT = `
|
|
11598
|
+
|
|
11599
|
+
[Hint] Use aft_conflicts to see all conflict regions across files in a single call.`;
|
|
11600
|
+
var GREP_HINT = `
|
|
11601
|
+
|
|
11602
|
+
[Hint] Use the grep tool instead of bash for faster indexed search.`;
|
|
11603
|
+
function maybeAppendConflictsHint(output) {
|
|
11604
|
+
if (!output.includes("Automatic merge failed; fix conflicts"))
|
|
11605
|
+
return output;
|
|
11606
|
+
if (!/^CONFLICT \(|^error: could not apply /m.test(output))
|
|
11607
|
+
return output;
|
|
11608
|
+
return output + CONFLICT_HINT;
|
|
11609
|
+
}
|
|
11610
|
+
function maybeAppendGrepHint(output, command) {
|
|
11611
|
+
const probe = command !== undefined ? command : output.slice(0, 300).split(`
|
|
11612
|
+
`)[0] ?? "";
|
|
11613
|
+
if (!/\b(rg|grep)\s/.test(probe))
|
|
11614
|
+
return output;
|
|
11615
|
+
return output + GREP_HINT;
|
|
11616
|
+
}
|
|
11596
11617
|
// ../aft-bridge/dist/bridge.js
|
|
11597
11618
|
import { spawn } from "node:child_process";
|
|
11598
11619
|
import { homedir } from "node:os";
|
|
11599
11620
|
import { join } from "node:path";
|
|
11600
11621
|
import { StringDecoder } from "node:string_decoder";
|
|
11622
|
+
|
|
11623
|
+
// ../aft-bridge/dist/status-bar.js
|
|
11624
|
+
var STATUS_BAR_HEARTBEAT_CALLS = 15;
|
|
11625
|
+
function createStatusBarEmitState() {
|
|
11626
|
+
return { callsSinceEmit: 0 };
|
|
11627
|
+
}
|
|
11628
|
+
function parseStatusBarCounts(value) {
|
|
11629
|
+
if (!value || typeof value !== "object")
|
|
11630
|
+
return;
|
|
11631
|
+
const record = value;
|
|
11632
|
+
const num = (key) => {
|
|
11633
|
+
const raw = record[key];
|
|
11634
|
+
return typeof raw === "number" && Number.isFinite(raw) ? raw : 0;
|
|
11635
|
+
};
|
|
11636
|
+
return {
|
|
11637
|
+
errors: num("errors"),
|
|
11638
|
+
warnings: num("warnings"),
|
|
11639
|
+
dead_code: num("dead_code"),
|
|
11640
|
+
unused_exports: num("unused_exports"),
|
|
11641
|
+
duplicates: num("duplicates"),
|
|
11642
|
+
todos: num("todos"),
|
|
11643
|
+
tier2_stale: record.tier2_stale === true
|
|
11644
|
+
};
|
|
11645
|
+
}
|
|
11646
|
+
function countsEqual(a, b) {
|
|
11647
|
+
return a.errors === b.errors && a.warnings === b.warnings && a.dead_code === b.dead_code && a.unused_exports === b.unused_exports && a.duplicates === b.duplicates && a.todos === b.todos && a.tier2_stale === b.tier2_stale;
|
|
11648
|
+
}
|
|
11649
|
+
function shouldEmitStatusBar(state, next) {
|
|
11650
|
+
const changed = state.last === undefined || !countsEqual(state.last, next);
|
|
11651
|
+
state.callsSinceEmit += 1;
|
|
11652
|
+
const heartbeat = state.callsSinceEmit >= STATUS_BAR_HEARTBEAT_CALLS;
|
|
11653
|
+
if (changed || heartbeat) {
|
|
11654
|
+
state.last = next;
|
|
11655
|
+
state.callsSinceEmit = 0;
|
|
11656
|
+
return true;
|
|
11657
|
+
}
|
|
11658
|
+
return false;
|
|
11659
|
+
}
|
|
11660
|
+
function formatStatusBar(counts) {
|
|
11661
|
+
const staleMark = counts.tier2_stale ? "~" : "";
|
|
11662
|
+
return `[AFT E${counts.errors} W${counts.warnings} | ` + `${staleMark}D${counts.dead_code} U${counts.unused_exports} C${counts.duplicates} | ` + `T${counts.todos}]`;
|
|
11663
|
+
}
|
|
11664
|
+
function statusBarLine(counts) {
|
|
11665
|
+
return `
|
|
11666
|
+
|
|
11667
|
+
${formatStatusBar(counts)}`;
|
|
11668
|
+
}
|
|
11669
|
+
|
|
11670
|
+
// ../aft-bridge/dist/bridge.js
|
|
11601
11671
|
var DEFAULT_BRIDGE_TIMEOUT_MS = 30000;
|
|
11602
11672
|
var BRIDGE_HANG_TIMEOUT_THRESHOLD = 2;
|
|
11603
11673
|
var SEMANTIC_TIMEOUT_SAFETY_MARGIN_MS = 5000;
|
|
@@ -11708,6 +11778,7 @@ class BinaryBridge {
|
|
|
11708
11778
|
configureWarningClients = new Map;
|
|
11709
11779
|
restartResetTimer = null;
|
|
11710
11780
|
lastChildActivityAt = 0;
|
|
11781
|
+
lastStatusBar;
|
|
11711
11782
|
consecutiveRequestTimeouts = 0;
|
|
11712
11783
|
errorPrefix;
|
|
11713
11784
|
logger;
|
|
@@ -12085,6 +12156,7 @@ class BinaryBridge {
|
|
|
12085
12156
|
this.spawnProcess(triggeringSessionId);
|
|
12086
12157
|
}
|
|
12087
12158
|
spawnProcess(triggeringSessionId) {
|
|
12159
|
+
this.lastStatusBar = undefined;
|
|
12088
12160
|
if (triggeringSessionId) {
|
|
12089
12161
|
this.sessionLogVia(triggeringSessionId, `Spawning binary: ${this.binaryPath} (cwd: ${this.cwd})`);
|
|
12090
12162
|
} else {
|
|
@@ -12286,6 +12358,7 @@ class BinaryBridge {
|
|
|
12286
12358
|
clearTimeout(entry.timer);
|
|
12287
12359
|
this.consecutiveRequestTimeouts = 0;
|
|
12288
12360
|
this.scheduleRestartCountReset();
|
|
12361
|
+
this.captureStatusBar(response);
|
|
12289
12362
|
entry.resolve(response);
|
|
12290
12363
|
} else if (typeof response.type === "string") {
|
|
12291
12364
|
this.logVia(`Ignoring unknown stdout push frame type: ${response.type}`);
|
|
@@ -12295,6 +12368,14 @@ class BinaryBridge {
|
|
|
12295
12368
|
}
|
|
12296
12369
|
}
|
|
12297
12370
|
}
|
|
12371
|
+
captureStatusBar(response) {
|
|
12372
|
+
const parsed = parseStatusBarCounts(response.status_bar);
|
|
12373
|
+
if (parsed)
|
|
12374
|
+
this.lastStatusBar = parsed;
|
|
12375
|
+
}
|
|
12376
|
+
getStatusBar() {
|
|
12377
|
+
return this.lastStatusBar;
|
|
12378
|
+
}
|
|
12298
12379
|
handleTimeout(triggeringSessionId) {
|
|
12299
12380
|
this.consecutiveRequestTimeouts = 0;
|
|
12300
12381
|
this.rejectAllPending(new Error(`${this.errorPrefix} bridge killed during sibling timeout — request aborted`));
|
|
@@ -13717,7 +13798,8 @@ class BridgePool {
|
|
|
13717
13798
|
onBashLongRunning: options.onBashLongRunning,
|
|
13718
13799
|
onBashPatternMatch: options.onBashPatternMatch,
|
|
13719
13800
|
errorPrefix: options.errorPrefix,
|
|
13720
|
-
logger: options.logger
|
|
13801
|
+
logger: options.logger,
|
|
13802
|
+
childEnv: options.childEnv
|
|
13721
13803
|
};
|
|
13722
13804
|
this.configOverrides = configOverrides;
|
|
13723
13805
|
if (Number.isFinite(this.idleTimeoutMs)) {
|
|
@@ -13850,6 +13932,9 @@ class BridgePool {
|
|
|
13850
13932
|
_testGetConfigOverrides() {
|
|
13851
13933
|
return { ...this.configOverrides };
|
|
13852
13934
|
}
|
|
13935
|
+
_testGetBridgeOptions() {
|
|
13936
|
+
return { ...this.bridgeOptions };
|
|
13937
|
+
}
|
|
13853
13938
|
}
|
|
13854
13939
|
function normalizeKey(projectRoot) {
|
|
13855
13940
|
const stripped = projectRoot.replace(/[/\\]+$/, "");
|
|
@@ -28473,8 +28558,8 @@ var LspExtensionSchema = exports_external.string().trim().min(1).refine((value)
|
|
|
28473
28558
|
message: "Extension must include characters other than leading dots"
|
|
28474
28559
|
});
|
|
28475
28560
|
var LspServerEntrySchema = exports_external.object({
|
|
28476
|
-
extensions: exports_external.array(LspExtensionSchema).min(1),
|
|
28477
|
-
binary: exports_external.string().trim().min(1),
|
|
28561
|
+
extensions: exports_external.array(LspExtensionSchema).min(1).optional(),
|
|
28562
|
+
binary: exports_external.string().trim().min(1).optional(),
|
|
28478
28563
|
args: exports_external.array(exports_external.string()).optional().default([]),
|
|
28479
28564
|
root_markers: exports_external.array(exports_external.string().trim().min(1)).optional().default([".git"]),
|
|
28480
28565
|
disabled: exports_external.boolean().optional().default(false),
|
|
@@ -28579,12 +28664,16 @@ function resolveLspConfigForConfigure(config2) {
|
|
|
28579
28664
|
const servers = Object.entries(config2.lsp?.servers ?? {}).map(([id, server]) => {
|
|
28580
28665
|
const entry = {
|
|
28581
28666
|
id,
|
|
28582
|
-
extensions: server.extensions.map(normalizeLspExtension),
|
|
28583
|
-
binary: server.binary,
|
|
28584
28667
|
args: server.args,
|
|
28585
28668
|
root_markers: server.root_markers,
|
|
28586
28669
|
disabled: server.disabled
|
|
28587
28670
|
};
|
|
28671
|
+
if (server.extensions && server.extensions.length > 0) {
|
|
28672
|
+
entry.extensions = server.extensions.map(normalizeLspExtension);
|
|
28673
|
+
}
|
|
28674
|
+
if (server.binary) {
|
|
28675
|
+
entry.binary = server.binary;
|
|
28676
|
+
}
|
|
28588
28677
|
if (server.env && Object.keys(server.env).length > 0) {
|
|
28589
28678
|
entry.env = server.env;
|
|
28590
28679
|
}
|
|
@@ -32015,29 +32104,6 @@ function normalizeToolMap(tools) {
|
|
|
32015
32104
|
}
|
|
32016
32105
|
return tools;
|
|
32017
32106
|
}
|
|
32018
|
-
|
|
32019
|
-
// src/shared/bash-hints.ts
|
|
32020
|
-
var CONFLICT_HINT = `
|
|
32021
|
-
|
|
32022
|
-
[Hint] Use aft_conflicts to see all conflict regions across files in a single call.`;
|
|
32023
|
-
var GREP_HINT = `
|
|
32024
|
-
|
|
32025
|
-
[Hint] Use the grep tool instead of bash for faster indexed search.`;
|
|
32026
|
-
function maybeAppendConflictsHint(output) {
|
|
32027
|
-
if (!output.includes("Automatic merge failed; fix conflicts"))
|
|
32028
|
-
return output;
|
|
32029
|
-
if (!/^CONFLICT \(|^error: could not apply /m.test(output))
|
|
32030
|
-
return output;
|
|
32031
|
-
return output + CONFLICT_HINT;
|
|
32032
|
-
}
|
|
32033
|
-
function maybeAppendGrepHint(output) {
|
|
32034
|
-
const firstLine = output.slice(0, 300).split(`
|
|
32035
|
-
`)[0] ?? "";
|
|
32036
|
-
if (!/\b(rg|grep)\s/.test(firstLine))
|
|
32037
|
-
return output;
|
|
32038
|
-
return output + GREP_HINT;
|
|
32039
|
-
}
|
|
32040
|
-
|
|
32041
32107
|
// src/shared/pty-cache.ts
|
|
32042
32108
|
var import_headless = __toESM(require_xterm_headless(), 1);
|
|
32043
32109
|
import * as fs2 from "node:fs/promises";
|
|
@@ -32631,6 +32697,22 @@ function registerShutdownCleanup(fn) {
|
|
|
32631
32697
|
};
|
|
32632
32698
|
}
|
|
32633
32699
|
|
|
32700
|
+
// src/status-bar-inject.ts
|
|
32701
|
+
var emitStateBySession = new Map;
|
|
32702
|
+
function statusBarSuffixForSession(sessionID, counts) {
|
|
32703
|
+
if (!counts)
|
|
32704
|
+
return "";
|
|
32705
|
+
let state = emitStateBySession.get(sessionID);
|
|
32706
|
+
if (!state) {
|
|
32707
|
+
state = createStatusBarEmitState();
|
|
32708
|
+
emitStateBySession.set(sessionID, state);
|
|
32709
|
+
}
|
|
32710
|
+
return shouldEmitStatusBar(state, counts) ? statusBarLine(counts) : "";
|
|
32711
|
+
}
|
|
32712
|
+
function clearStatusBarSession(sessionID) {
|
|
32713
|
+
emitStateBySession.delete(sessionID);
|
|
32714
|
+
}
|
|
32715
|
+
|
|
32634
32716
|
// src/tools/ast.ts
|
|
32635
32717
|
import { tool as tool3 } from "@opencode-ai/plugin";
|
|
32636
32718
|
|
|
@@ -36905,6 +36987,7 @@ function buildWorkflowHints(opts) {
|
|
|
36905
36987
|
}
|
|
36906
36988
|
if (hasInspect) {
|
|
36907
36989
|
sections.push("**Codebase health & diagnostics**: AFT does not surface compile/type errors automatically after edits — pull them with `aft_inspect`. Run it after a batch of edits and before you run tests or commit, when starting in unfamiliar code, or before a refactor/review. One call summarizes diagnostics (compile/type errors), TODOs, metrics, dead code, unused exports, and duplicates; pass `sections` for focused drill-down and `scope` to actively pull diagnostics for a specific file or directory. Its diagnostics are a fast checkpoint, not the authority — a clean `tsc` / `cargo check` / `pyright` run is the real gate. Treat `stale_categories` as a genuine stale-cache signal while an async Tier 2 refresh catches up.");
|
|
36990
|
+
sections.push("**AFT status bar**: tool results may end with a one-line health bar `[AFT E<errors> W<warnings> | D<dead-code> U<unused-exports> C<clone/dup-groups> | T<todos>]` — an IDE-style glance that appears when a count changes. `E`/`W` are live LSP diagnostics for files touched this session (your universal compile-error signal across every language with an LSP). A `~` before `D` means the dead-code/unused/dup counts predate your latest edit — run `aft_inspect` for current numbers and detail. When `E>0`, you likely just introduced errors; investigate before moving on.");
|
|
36908
36991
|
}
|
|
36909
36992
|
if (hasNavigate) {
|
|
36910
36993
|
sections.push([
|
|
@@ -36984,13 +37067,14 @@ var PLUGIN_VERSION = (() => {
|
|
|
36984
37067
|
return "0.0.0";
|
|
36985
37068
|
}
|
|
36986
37069
|
})();
|
|
36987
|
-
var ANNOUNCEMENT_VERSION = "0.
|
|
37070
|
+
var ANNOUNCEMENT_VERSION = "0.35.0";
|
|
36988
37071
|
var ANNOUNCEMENT_FEATURES = [
|
|
36989
|
-
"
|
|
36990
|
-
"New
|
|
36991
|
-
"`
|
|
36992
|
-
"
|
|
36993
|
-
"`
|
|
37072
|
+
"Leaner semantic indexing — local embedding now uses about half the CPU and a fraction of the peak memory, with no change to results and no re-index needed.",
|
|
37073
|
+
"New IDE-style status bar on tool results — live LSP errors/warnings plus dead-code, unused-export, duplicate, and TODO counts at a glance.",
|
|
37074
|
+
"`aft_inspect` is more accurate (dead-code reachability, duplicate collapse, ranked findings) and a background scheduler keeps its counts fresh during long sessions.",
|
|
37075
|
+
"The TUI sidebar is now collapsible — click the `AFT` header to toggle a condensed view.",
|
|
37076
|
+
"`npx @cortexkit/aft --version` reports CLI, binary, and per-harness versions; `doctor --issue` can scope its report to a single session.",
|
|
37077
|
+
"New SCSS support and `.inc` files parsed as PHP."
|
|
36994
37078
|
];
|
|
36995
37079
|
var ANNOUNCEMENT_FOOTER = "Join us on Discord: https://discord.gg/DSa65w8wuf";
|
|
36996
37080
|
var plugin = async (input) => initializePluginForDirectory(input);
|
|
@@ -37398,6 +37482,7 @@ Install: ${getManualInstallHint()}`).catch(() => {});
|
|
|
37398
37482
|
const sessionID = extractSessionID(eventInput.event.properties);
|
|
37399
37483
|
if ((eventType === "session.deleted" || eventType === "session.shutdown") && sessionID) {
|
|
37400
37484
|
inspectTier2Idle.clear(sessionID);
|
|
37485
|
+
clearStatusBarSession(sessionID);
|
|
37401
37486
|
return;
|
|
37402
37487
|
}
|
|
37403
37488
|
if (eventType !== "session.idle")
|
|
@@ -37460,6 +37545,12 @@ Install: ${getManualInstallHint()}`).catch(() => {});
|
|
|
37460
37545
|
}
|
|
37461
37546
|
const sessionDir = getSessionDirectoryCached(toolInput.sessionID) ?? input.directory;
|
|
37462
37547
|
await appendInTurnBgCompletions({ ctx, directory: sessionDir, sessionID: toolInput.sessionID }, output);
|
|
37548
|
+
if (output.output !== undefined) {
|
|
37549
|
+
const activeBridge = ctx.pool.getActiveBridgeForRoot(sessionDir);
|
|
37550
|
+
const suffix = statusBarSuffixForSession(toolInput.sessionID, activeBridge?.getStatusBar());
|
|
37551
|
+
if (suffix)
|
|
37552
|
+
output.output += suffix;
|
|
37553
|
+
}
|
|
37463
37554
|
},
|
|
37464
37555
|
config: async (config2) => {
|
|
37465
37556
|
if (!config2 || typeof config2 !== "object")
|
|
@@ -1,24 +1,2 @@
|
|
|
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;
|
|
1
|
+
export { maybeAppendConflictsHint, maybeAppendGrepHint } from "@cortexkit/aft-bridge";
|
|
24
2
|
//# sourceMappingURL=bash-hints.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"bash-hints.d.ts","sourceRoot":"","sources":["../../src/shared/bash-hints.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"bash-hints.d.ts","sourceRoot":"","sources":["../../src/shared/bash-hints.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,wBAAwB,EAAE,mBAAmB,EAAE,MAAM,uBAAuB,CAAC"}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { type StatusBarCounts } from "@cortexkit/aft-bridge";
|
|
2
|
+
/**
|
|
3
|
+
* Decide whether to surface the bar for this session+counts and, if so, return
|
|
4
|
+
* the line to append to the tool output. Returns `""` when suppressed
|
|
5
|
+
* (unchanged since last emit and heartbeat not elapsed) or when counts are
|
|
6
|
+
* absent (no inspect scan has populated Tier-2 yet).
|
|
7
|
+
*/
|
|
8
|
+
export declare function statusBarSuffixForSession(sessionID: string, counts: StatusBarCounts | undefined): string;
|
|
9
|
+
/** Drop a session's emit state (on session delete/shutdown). */
|
|
10
|
+
export declare function clearStatusBarSession(sessionID: string): void;
|
|
11
|
+
//# sourceMappingURL=status-bar-inject.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"status-bar-inject.d.ts","sourceRoot":"","sources":["../src/status-bar-inject.ts"],"names":[],"mappings":"AAOA,OAAO,EAEL,KAAK,eAAe,EAIrB,MAAM,uBAAuB,CAAC;AAI/B;;;;;GAKG;AACH,wBAAgB,yBAAyB,CACvC,SAAS,EAAE,MAAM,EACjB,MAAM,EAAE,eAAe,GAAG,SAAS,GAClC,MAAM,CAQR;AAED,gEAAgE;AAChE,wBAAgB,qBAAqB,CAAC,SAAS,EAAE,MAAM,GAAG,IAAI,CAE7D"}
|
package/dist/tui.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
// src/tui/index.tsx
|
|
2
2
|
import { createMemo as createMemo2, createSignal as createSignal2, onCleanup as onCleanup2 } from "solid-js";
|
|
3
3
|
// package.json
|
|
4
|
-
var version = "0.
|
|
4
|
+
var version = "0.35.0";
|
|
5
5
|
|
|
6
6
|
// src/shared/rpc-client.ts
|
|
7
7
|
import { existsSync, readdirSync, readFileSync, unlinkSync } from "node:fs";
|
|
@@ -560,6 +560,37 @@ var SectionHeader = (props) => /* @__PURE__ */ jsxDEV("box", {
|
|
|
560
560
|
}, undefined, false, undefined, this)
|
|
561
561
|
}, undefined, false, undefined, this)
|
|
562
562
|
}, undefined, false, undefined, this);
|
|
563
|
+
function toneColor(theme, tone) {
|
|
564
|
+
switch (tone) {
|
|
565
|
+
case "ok":
|
|
566
|
+
return theme.success ?? theme.accent;
|
|
567
|
+
case "warn":
|
|
568
|
+
return theme.warning;
|
|
569
|
+
case "err":
|
|
570
|
+
return theme.error;
|
|
571
|
+
default:
|
|
572
|
+
return theme.textMuted;
|
|
573
|
+
}
|
|
574
|
+
}
|
|
575
|
+
var CollapsedRow = (props) => /* @__PURE__ */ jsxDEV("box", {
|
|
576
|
+
width: "100%",
|
|
577
|
+
flexDirection: "row",
|
|
578
|
+
justifyContent: "space-between",
|
|
579
|
+
children: [
|
|
580
|
+
/* @__PURE__ */ jsxDEV("text", {
|
|
581
|
+
fg: props.theme.textMuted,
|
|
582
|
+
children: props.label
|
|
583
|
+
}, undefined, false, undefined, this),
|
|
584
|
+
props.children
|
|
585
|
+
]
|
|
586
|
+
}, undefined, true, undefined, this);
|
|
587
|
+
function collapsedCompressionValue(compression) {
|
|
588
|
+
if (!compression || compression.project.events <= 0)
|
|
589
|
+
return null;
|
|
590
|
+
const { savings_tokens, original_tokens } = compression.project;
|
|
591
|
+
const pct = original_tokens > 0 ? Math.round(savings_tokens / original_tokens * 100) : 0;
|
|
592
|
+
return `${formatCount(savings_tokens)} / ${pct}%`;
|
|
593
|
+
}
|
|
563
594
|
function resolveTuiStorageDir() {
|
|
564
595
|
return resolveCortexKitStorageRoot();
|
|
565
596
|
}
|
|
@@ -579,8 +610,12 @@ function scopedSidebarSnapshot(scoped, directory, sessionID) {
|
|
|
579
610
|
return null;
|
|
580
611
|
return scoped.snapshot;
|
|
581
612
|
}
|
|
613
|
+
function shouldSuppressUninitializedDowngrade(incomingCacheRole, haveInitializedForContext) {
|
|
614
|
+
return incomingCacheRole === "not_initialized" && haveInitializedForContext;
|
|
615
|
+
}
|
|
582
616
|
var SidebarContent = (props) => {
|
|
583
617
|
const [status, setStatus] = createSignal(null);
|
|
618
|
+
const [collapsed, setCollapsed] = createSignal(false);
|
|
584
619
|
let inflight = null;
|
|
585
620
|
let generation = 0;
|
|
586
621
|
let debounceTimer;
|
|
@@ -637,6 +672,10 @@ var SidebarContent = (props) => {
|
|
|
637
672
|
return;
|
|
638
673
|
if (response && response.success !== false) {
|
|
639
674
|
const snapshot = coerceAftStatus(response);
|
|
675
|
+
const current = status();
|
|
676
|
+
const haveGoodForContext = current !== null && current.directory === directory && current.sessionID === sid && current.snapshot.cache_role !== "not_initialized";
|
|
677
|
+
if (shouldSuppressUninitializedDowngrade(snapshot.cache_role, haveGoodForContext))
|
|
678
|
+
return;
|
|
640
679
|
setStatus({ directory, sessionID: sid, snapshot });
|
|
641
680
|
requestRender();
|
|
642
681
|
}
|
|
@@ -749,6 +788,10 @@ var SidebarContent = (props) => {
|
|
|
749
788
|
flexDirection: "row",
|
|
750
789
|
justifyContent: "space-between",
|
|
751
790
|
alignItems: "center",
|
|
791
|
+
onMouseDown: () => {
|
|
792
|
+
if (!notInitialized())
|
|
793
|
+
setCollapsed((x) => !x);
|
|
794
|
+
},
|
|
752
795
|
children: [
|
|
753
796
|
/* @__PURE__ */ jsxDEV("box", {
|
|
754
797
|
flexDirection: "row",
|
|
@@ -761,8 +804,11 @@ var SidebarContent = (props) => {
|
|
|
761
804
|
children: /* @__PURE__ */ jsxDEV("text", {
|
|
762
805
|
fg: props.theme.background,
|
|
763
806
|
children: /* @__PURE__ */ jsxDEV("b", {
|
|
764
|
-
children:
|
|
765
|
-
|
|
807
|
+
children: [
|
|
808
|
+
notInitialized() ? "" : collapsed() ? "▶ " : "▼ ",
|
|
809
|
+
"AFT"
|
|
810
|
+
]
|
|
811
|
+
}, undefined, true, undefined, this)
|
|
766
812
|
}, undefined, false, undefined, this)
|
|
767
813
|
}, undefined, false, undefined, this),
|
|
768
814
|
s()?.degraded && /* @__PURE__ */ jsxDEV("box", {
|
|
@@ -807,7 +853,39 @@ var SidebarContent = (props) => {
|
|
|
807
853
|
children: s().message || "AFT bridge is now spawned lazily, information here will be populated after first tool call."
|
|
808
854
|
}, undefined, false, undefined, this)
|
|
809
855
|
}, undefined, false, undefined, this),
|
|
810
|
-
!notInitialized() && /* @__PURE__ */ jsxDEV(
|
|
856
|
+
!notInitialized() && collapsed() && /* @__PURE__ */ jsxDEV("box", {
|
|
857
|
+
width: "100%",
|
|
858
|
+
flexDirection: "column",
|
|
859
|
+
children: [
|
|
860
|
+
/* @__PURE__ */ jsxDEV(CollapsedRow, {
|
|
861
|
+
theme: props.theme,
|
|
862
|
+
label: "Search Index",
|
|
863
|
+
children: /* @__PURE__ */ jsxDEV("text", {
|
|
864
|
+
fg: toneColor(props.theme, searchStatus().tone),
|
|
865
|
+
children: "●"
|
|
866
|
+
}, undefined, false, undefined, this)
|
|
867
|
+
}, undefined, false, undefined, this),
|
|
868
|
+
/* @__PURE__ */ jsxDEV(CollapsedRow, {
|
|
869
|
+
theme: props.theme,
|
|
870
|
+
label: "Semantic Index",
|
|
871
|
+
children: /* @__PURE__ */ jsxDEV("text", {
|
|
872
|
+
fg: toneColor(props.theme, semanticStatus().tone),
|
|
873
|
+
children: "●"
|
|
874
|
+
}, undefined, false, undefined, this)
|
|
875
|
+
}, undefined, false, undefined, this),
|
|
876
|
+
collapsedCompressionValue(s()?.compression) && /* @__PURE__ */ jsxDEV(CollapsedRow, {
|
|
877
|
+
theme: props.theme,
|
|
878
|
+
label: "Compression",
|
|
879
|
+
children: /* @__PURE__ */ jsxDEV("text", {
|
|
880
|
+
fg: props.theme.textMuted,
|
|
881
|
+
children: /* @__PURE__ */ jsxDEV("b", {
|
|
882
|
+
children: collapsedCompressionValue(s()?.compression)
|
|
883
|
+
}, undefined, false, undefined, this)
|
|
884
|
+
}, undefined, false, undefined, this)
|
|
885
|
+
}, undefined, false, undefined, this)
|
|
886
|
+
]
|
|
887
|
+
}, undefined, true, undefined, this),
|
|
888
|
+
!notInitialized() && !collapsed() && /* @__PURE__ */ jsxDEV(Fragment, {
|
|
811
889
|
children: [
|
|
812
890
|
/* @__PURE__ */ jsxDEV(SectionHeader, {
|
|
813
891
|
theme: props.theme,
|
|
@@ -1018,7 +1096,12 @@ var StatusDialog = (props) => {
|
|
|
1018
1096
|
if (controller.signal.aborted || requestGeneration !== pollGeneration)
|
|
1019
1097
|
return;
|
|
1020
1098
|
if (response.success !== false) {
|
|
1021
|
-
|
|
1099
|
+
const snapshot = coerceAftStatus(response);
|
|
1100
|
+
const current = status();
|
|
1101
|
+
if (shouldSuppressUninitializedDowngrade(snapshot.cache_role, current !== null && current.cache_role !== "not_initialized")) {
|
|
1102
|
+
return;
|
|
1103
|
+
}
|
|
1104
|
+
setStatus(snapshot);
|
|
1022
1105
|
setError(null);
|
|
1023
1106
|
}
|
|
1024
1107
|
} catch {
|
|
@@ -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,CA+FzE;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.35.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",
|
|
@@ -30,19 +30,19 @@
|
|
|
30
30
|
},
|
|
31
31
|
"dependencies": {
|
|
32
32
|
"@clack/prompts": "^1.2.0",
|
|
33
|
-
"@cortexkit/aft-bridge": "0.
|
|
33
|
+
"@cortexkit/aft-bridge": "0.35.0",
|
|
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.
|
|
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.35.0",
|
|
41
|
+
"@cortexkit/aft-darwin-x64": "0.35.0",
|
|
42
|
+
"@cortexkit/aft-linux-arm64": "0.35.0",
|
|
43
|
+
"@cortexkit/aft-linux-x64": "0.35.0",
|
|
44
|
+
"@cortexkit/aft-win32-arm64": "0.35.0",
|
|
45
|
+
"@cortexkit/aft-win32-x64": "0.35.0"
|
|
46
46
|
},
|
|
47
47
|
"devDependencies": {
|
|
48
48
|
"@opencode-ai/plugin": "^1.15.11",
|
package/src/shared/bash-hints.ts
CHANGED
|
@@ -1,45 +1,4 @@
|
|
|
1
|
-
//
|
|
2
|
-
//
|
|
3
|
-
//
|
|
4
|
-
|
|
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
|
-
}
|
|
1
|
+
// Bash-output hint nudges now live in the shared bridge package so both plugin
|
|
2
|
+
// hosts use one implementation. Re-exported here to keep the existing OpenCode
|
|
3
|
+
// import path stable.
|
|
4
|
+
export { maybeAppendConflictsHint, maybeAppendGrepHint } from "@cortexkit/aft-bridge";
|
package/src/tui/index.tsx
CHANGED
|
@@ -17,6 +17,7 @@ import {
|
|
|
17
17
|
createAftSidebarSlot,
|
|
18
18
|
formatCompressionSidebarRows,
|
|
19
19
|
resolveTuiStorageDir,
|
|
20
|
+
shouldSuppressUninitializedDowngrade,
|
|
20
21
|
} from "./sidebar";
|
|
21
22
|
|
|
22
23
|
// The TUI talks to the server plugin via AftRpcClient. The client reads the
|
|
@@ -160,7 +161,20 @@ const StatusDialog = (props: StatusDialogProps) => {
|
|
|
160
161
|
);
|
|
161
162
|
if (controller.signal.aborted || requestGeneration !== pollGeneration) return;
|
|
162
163
|
if ((response as Record<string, unknown>).success !== false) {
|
|
163
|
-
|
|
164
|
+
const snapshot = coerceAftStatus(response as Record<string, unknown>);
|
|
165
|
+
// Stale-while-revalidate: don't downgrade a good snapshot to a transient
|
|
166
|
+
// `not_initialized` (bridge mid-respawn / session-dir key miss) — it
|
|
167
|
+
// arrives as success:true and would blank the dialog until the next poll.
|
|
168
|
+
const current = status();
|
|
169
|
+
if (
|
|
170
|
+
shouldSuppressUninitializedDowngrade(
|
|
171
|
+
snapshot.cache_role,
|
|
172
|
+
current !== null && current.cache_role !== "not_initialized",
|
|
173
|
+
)
|
|
174
|
+
) {
|
|
175
|
+
return;
|
|
176
|
+
}
|
|
177
|
+
setStatus(snapshot);
|
|
164
178
|
setError(null);
|
|
165
179
|
}
|
|
166
180
|
} catch {
|
package/src/tui/sidebar.tsx
CHANGED
|
@@ -142,6 +142,43 @@ const SectionHeader = (props: { theme: TuiThemeCurrent; title: string; marginTop
|
|
|
142
142
|
</box>
|
|
143
143
|
);
|
|
144
144
|
|
|
145
|
+
// Map a status tone to a theme color — used for the collapsed-view status dots.
|
|
146
|
+
function toneColor(theme: TuiThemeCurrent, tone: "ok" | "warn" | "err" | "muted"): string {
|
|
147
|
+
switch (tone) {
|
|
148
|
+
case "ok":
|
|
149
|
+
return theme.success ?? theme.accent;
|
|
150
|
+
case "warn":
|
|
151
|
+
return theme.warning;
|
|
152
|
+
case "err":
|
|
153
|
+
return theme.error;
|
|
154
|
+
default:
|
|
155
|
+
return theme.textMuted;
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
// Collapsed-view row: label on the left, a status dot (or compact value) on the
|
|
160
|
+
// right. Mirrors the expanded StatRow layout so the columns line up.
|
|
161
|
+
const CollapsedRow = (props: { theme: TuiThemeCurrent; label: string; children: JSX.Element }) => (
|
|
162
|
+
<box width="100%" flexDirection="row" justifyContent="space-between">
|
|
163
|
+
<text fg={props.theme.textMuted}>{props.label}</text>
|
|
164
|
+
{props.children}
|
|
165
|
+
</box>
|
|
166
|
+
);
|
|
167
|
+
|
|
168
|
+
// Compact "saved / ratio" string for the collapsed Compression row — e.g.
|
|
169
|
+
// "7.6M / 64%". Uses the local `formatCount` (not the aft-bridge token
|
|
170
|
+
// formatter) so the TUI bundle doesn't pull the bridge barrel, which exports
|
|
171
|
+
// URL-fetch helpers unsuitable for Bun's TUI runtime. Returns null when no
|
|
172
|
+
// compression has been recorded yet.
|
|
173
|
+
export function collapsedCompressionValue(
|
|
174
|
+
compression: StatusCompression | undefined,
|
|
175
|
+
): string | null {
|
|
176
|
+
if (!compression || compression.project.events <= 0) return null;
|
|
177
|
+
const { savings_tokens, original_tokens } = compression.project;
|
|
178
|
+
const pct = original_tokens > 0 ? Math.round((savings_tokens / original_tokens) * 100) : 0;
|
|
179
|
+
return `${formatCount(savings_tokens)} / ${pct}%`;
|
|
180
|
+
}
|
|
181
|
+
|
|
145
182
|
// v0.27 moved AFT storage to the CortexKit root. TUI code must use a
|
|
146
183
|
// lightweight local path helper rather than the shared bridge barrel, which
|
|
147
184
|
// also exports URL-fetch helpers unsuitable for Bun's TUI runtime.
|
|
@@ -177,6 +214,22 @@ export function scopedSidebarSnapshot(
|
|
|
177
214
|
return scoped.snapshot;
|
|
178
215
|
}
|
|
179
216
|
|
|
217
|
+
/**
|
|
218
|
+
* Stale-while-revalidate guard. A transient `not_initialized` snapshot (bridge
|
|
219
|
+
* mid-respawn after a binary swap, or a momentary session-dir key miss) arrives
|
|
220
|
+
* over RPC as `success: true`, so a naive `setStatus` would overwrite a good
|
|
221
|
+
* snapshot and collapse the panel to the lazy-bridge placeholder — the blank
|
|
222
|
+
* flicker that recovers on the next poll. Suppress the downgrade only when we
|
|
223
|
+
* already hold initialized data for the same context; never blocks the first
|
|
224
|
+
* real snapshot, and a genuine context switch clears separately.
|
|
225
|
+
*/
|
|
226
|
+
export function shouldSuppressUninitializedDowngrade(
|
|
227
|
+
incomingCacheRole: string | undefined,
|
|
228
|
+
haveInitializedForContext: boolean,
|
|
229
|
+
): boolean {
|
|
230
|
+
return incomingCacheRole === "not_initialized" && haveInitializedForContext;
|
|
231
|
+
}
|
|
232
|
+
|
|
180
233
|
const SidebarContent = (props: {
|
|
181
234
|
api: TuiPluginApi;
|
|
182
235
|
sessionID: () => string;
|
|
@@ -184,6 +237,9 @@ const SidebarContent = (props: {
|
|
|
184
237
|
pluginVersion: string;
|
|
185
238
|
}) => {
|
|
186
239
|
const [status, setStatus] = createSignal<ScopedSidebarStatus | null>(null);
|
|
240
|
+
// Collapsed/expanded toggle — local UI state (not persisted), mirroring
|
|
241
|
+
// OpenCode's native MCP sidebar section. Default expanded.
|
|
242
|
+
const [collapsed, setCollapsed] = createSignal(false);
|
|
187
243
|
let inflight: {
|
|
188
244
|
controller: AbortController;
|
|
189
245
|
generation: number;
|
|
@@ -251,6 +307,16 @@ const SidebarContent = (props: {
|
|
|
251
307
|
if (currentDirectory() !== directory || props.sessionID() !== sid) return;
|
|
252
308
|
if (response && (response as Record<string, unknown>).success !== false) {
|
|
253
309
|
const snapshot = coerceAftStatus(response as Record<string, unknown>);
|
|
310
|
+
// Stale-while-revalidate: keep the last-good snapshot instead of
|
|
311
|
+
// flickering to the lazy-bridge placeholder on a transient
|
|
312
|
+
// not_initialized. See shouldSuppressUninitializedDowngrade.
|
|
313
|
+
const current = status();
|
|
314
|
+
const haveGoodForContext =
|
|
315
|
+
current !== null &&
|
|
316
|
+
current.directory === directory &&
|
|
317
|
+
current.sessionID === sid &&
|
|
318
|
+
current.snapshot.cache_role !== "not_initialized";
|
|
319
|
+
if (shouldSuppressUninitializedDowngrade(snapshot.cache_role, haveGoodForContext)) return;
|
|
254
320
|
setStatus({ directory, sessionID: sid, snapshot });
|
|
255
321
|
requestRender();
|
|
256
322
|
}
|
|
@@ -394,12 +460,27 @@ const SidebarContent = (props: {
|
|
|
394
460
|
paddingLeft={1}
|
|
395
461
|
paddingRight={1}
|
|
396
462
|
>
|
|
397
|
-
{/* Header: AFT badge + binary version + degraded badge
|
|
398
|
-
|
|
463
|
+
{/* Header: triangle toggle + AFT badge + binary version + degraded badge.
|
|
464
|
+
Clicking the header row collapses/expands the panel (mirrors OpenCode's
|
|
465
|
+
native MCP sidebar section). Only interactive once initialized — the
|
|
466
|
+
lazy-bridge placeholder has nothing to collapse. */}
|
|
467
|
+
<box
|
|
468
|
+
flexDirection="row"
|
|
469
|
+
justifyContent="space-between"
|
|
470
|
+
alignItems="center"
|
|
471
|
+
onMouseDown={() => {
|
|
472
|
+
if (!notInitialized()) setCollapsed((x) => !x);
|
|
473
|
+
}}
|
|
474
|
+
>
|
|
399
475
|
<box flexDirection="row" alignItems="center">
|
|
476
|
+
{/* Triangle lives inside the accent badge so the toggle reads as one
|
|
477
|
+
unit: "▶ AFT" / "▼ AFT". Hidden pre-init (nothing to collapse). */}
|
|
400
478
|
<box paddingLeft={1} paddingRight={1} backgroundColor={props.theme.accent}>
|
|
401
479
|
<text fg={props.theme.background}>
|
|
402
|
-
<b>
|
|
480
|
+
<b>
|
|
481
|
+
{notInitialized() ? "" : collapsed() ? "▶ " : "▼ "}
|
|
482
|
+
AFT
|
|
483
|
+
</b>
|
|
403
484
|
</text>
|
|
404
485
|
</box>
|
|
405
486
|
{s()?.degraded && (
|
|
@@ -445,8 +526,29 @@ const SidebarContent = (props: {
|
|
|
445
526
|
</box>
|
|
446
527
|
)}
|
|
447
528
|
|
|
529
|
+
{/* Collapsed view — condensed status dots + compact compression. Shown
|
|
530
|
+
only when initialized AND collapsed. Three rows mirroring the section
|
|
531
|
+
order of the expanded grid. */}
|
|
532
|
+
{!notInitialized() && collapsed() && (
|
|
533
|
+
<box width="100%" flexDirection="column">
|
|
534
|
+
<CollapsedRow theme={props.theme} label="Search Index">
|
|
535
|
+
<text fg={toneColor(props.theme, searchStatus().tone)}>●</text>
|
|
536
|
+
</CollapsedRow>
|
|
537
|
+
<CollapsedRow theme={props.theme} label="Semantic Index">
|
|
538
|
+
<text fg={toneColor(props.theme, semanticStatus().tone)}>●</text>
|
|
539
|
+
</CollapsedRow>
|
|
540
|
+
{collapsedCompressionValue(s()?.compression) && (
|
|
541
|
+
<CollapsedRow theme={props.theme} label="Compression">
|
|
542
|
+
<text fg={props.theme.textMuted}>
|
|
543
|
+
<b>{collapsedCompressionValue(s()?.compression)}</b>
|
|
544
|
+
</text>
|
|
545
|
+
</CollapsedRow>
|
|
546
|
+
)}
|
|
547
|
+
</box>
|
|
548
|
+
)}
|
|
549
|
+
|
|
448
550
|
{/* Search index */}
|
|
449
|
-
{!notInitialized() && (
|
|
551
|
+
{!notInitialized() && !collapsed() && (
|
|
450
552
|
<>
|
|
451
553
|
<SectionHeader theme={props.theme} title="Search Index" />
|
|
452
554
|
<StatRow
|