@cortexkit/aft-opencode 0.18.1 → 0.18.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/bg-notifications.d.ts +13 -0
- package/dist/bg-notifications.d.ts.map +1 -1
- package/dist/bridge.d.ts +26 -0
- package/dist/bridge.d.ts.map +1 -1
- package/dist/config.d.ts +1 -0
- package/dist/config.d.ts.map +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +289 -61
- package/dist/logger.d.ts +3 -3
- package/dist/logger.d.ts.map +1 -1
- package/dist/lsp.d.ts +1 -1
- package/dist/lsp.d.ts.map +1 -1
- package/dist/notifications.d.ts.map +1 -1
- package/dist/pool.d.ts.map +1 -1
- package/dist/shared/last-user-model.d.ts +38 -0
- package/dist/shared/last-user-model.d.ts.map +1 -0
- package/dist/shared/rpc-server.d.ts.map +1 -1
- package/dist/tools/bash.d.ts +2 -0
- package/dist/tools/bash.d.ts.map +1 -1
- package/dist/tools/hoisted.d.ts.map +1 -1
- package/dist/tools/refactoring.d.ts.map +1 -1
- package/dist/tui.js +10 -7
- package/package.json +8 -6
- package/src/logger.ts +104 -0
- package/src/shared/last-user-model.ts +133 -0
- package/src/shared/rpc-server.ts +3 -2
|
@@ -7,6 +7,10 @@ export interface BgCompletion {
|
|
|
7
7
|
duration_ms?: number;
|
|
8
8
|
runtime_ms?: number;
|
|
9
9
|
runtime?: number;
|
|
10
|
+
/** Tail of stdout+stderr captured at completion (≤300 bytes from Rust). */
|
|
11
|
+
output_preview?: string;
|
|
12
|
+
/** True when the captured tail is shorter than the actual output. */
|
|
13
|
+
output_truncated?: boolean;
|
|
10
14
|
}
|
|
11
15
|
type SessionBgState = {
|
|
12
16
|
outstandingTaskIds: Set<string>;
|
|
@@ -16,6 +20,11 @@ type SessionBgState = {
|
|
|
16
20
|
firstCompletionAt: number | null;
|
|
17
21
|
scheduledFireAt: number | null;
|
|
18
22
|
scheduledCompletionCount: number;
|
|
23
|
+
retryDelayMs: number | null;
|
|
24
|
+
unknownCompletions: Array<{
|
|
25
|
+
completion: BgCompletion;
|
|
26
|
+
receivedAt: number;
|
|
27
|
+
}>;
|
|
19
28
|
lastSeenAt: number;
|
|
20
29
|
};
|
|
21
30
|
export declare const sessionBgStates: Map<string, SessionBgState>;
|
|
@@ -24,9 +33,13 @@ interface DrainContext {
|
|
|
24
33
|
ctx: PluginContext;
|
|
25
34
|
directory: string;
|
|
26
35
|
sessionID: string;
|
|
36
|
+
isActive?: () => boolean;
|
|
27
37
|
}
|
|
28
38
|
export declare function trackBgTask(sessionID: string | undefined, taskId: string): void;
|
|
29
39
|
export declare function ingestBgCompletions(sessionID: string | undefined, completions: unknown): BgCompletion[];
|
|
40
|
+
export declare function handlePushedBgCompletion(drainContext: DrainContext & {
|
|
41
|
+
client: unknown;
|
|
42
|
+
}, completion: unknown): Promise<void>;
|
|
30
43
|
export declare function appendInTurnBgCompletions(drainContext: DrainContext, output: {
|
|
31
44
|
output?: string;
|
|
32
45
|
} | undefined): Promise<void>;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"bg-notifications.d.ts","sourceRoot":"","sources":["../src/bg-notifications.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"bg-notifications.d.ts","sourceRoot":"","sources":["../src/bg-notifications.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AAEhD,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;CAC5B;AAED,KAAK,cAAc,GAAG;IACpB,kBAAkB,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC;IAChC,kBAAkB,EAAE,YAAY,EAAE,CAAC;IACnC,aAAa,EAAE,MAAM,CAAC,OAAO,GAAG,IAAI,CAAC;IACrC,iBAAiB,EAAE,OAAO,CAAC;IAC3B,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,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;AAQ3D,UAAU,YAAY;IACpB,GAAG,EAAE,aAAa,CAAC;IACnB,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;IAClB,QAAQ,CAAC,EAAE,MAAM,OAAO,CAAC;CAC1B;AASD,wBAAgB,WAAW,CAAC,SAAS,EAAE,MAAM,GAAG,SAAS,EAAE,MAAM,EAAE,MAAM,GAAG,IAAI,CAgB/E;AAED,wBAAgB,mBAAmB,CACjC,SAAS,EAAE,MAAM,GAAG,SAAS,EAC7B,WAAW,EAAE,OAAO,GACnB,YAAY,EAAE,CAoBhB;AAED,wBAAsB,wBAAwB,CAC5C,YAAY,EAAE,YAAY,GAAG;IAAE,MAAM,EAAE,OAAO,CAAA;CAAE,EAChD,UAAU,EAAE,OAAO,GAClB,OAAO,CAAC,IAAI,CAAC,CAGf;AAED,wBAAsB,yBAAyB,CAC7C,YAAY,EAAE,YAAY,EAC1B,MAAM,EAAE;IAAE,MAAM,CAAC,EAAE,MAAM,CAAA;CAAE,GAAG,SAAS,GACtC,OAAO,CAAC,IAAI,CAAC,CAaf;AAED,wBAAsB,uBAAuB,CAC3C,YAAY,EAAE,YAAY,GAAG;IAAE,MAAM,EAAE,OAAO,CAAA;CAAE,GAC/C,OAAO,CAAC,IAAI,CAAC,CAEf;AAgDD,wBAAgB,WAAW,CAAC,SAAS,EAAE,MAAM,GAAG,SAAS,GAAG,IAAI,CAE/D;AAED,wBAAgB,oBAAoB,CAAC,WAAW,EAAE,SAAS,YAAY,EAAE,GAAG,MAAM,CASjF;AAED,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,OAAO,GAAG,MAAM,GAAG,SAAS,CAcnE;AAED,wBAAgB,kCAAkC,IAAI,IAAI,CAKzD"}
|
package/dist/bridge.d.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { BgCompletion } from "./bg-notifications.js";
|
|
1
2
|
/**
|
|
2
3
|
* Compare two semver version strings (major.minor.patch plus pre-release).
|
|
3
4
|
* Returns: negative if a < b, 0 if equal, positive if a > b.
|
|
@@ -20,6 +21,12 @@ export interface BridgeOptions {
|
|
|
20
21
|
onVersionMismatch?: (binaryVersion: string, minVersion: string) => void;
|
|
21
22
|
/** Called after the first successful configure returns user-visible warnings. */
|
|
22
23
|
onConfigureWarnings?: (context: ConfigureWarningsContext) => void | Promise<void>;
|
|
24
|
+
/** Called for server-pushed background bash completions. */
|
|
25
|
+
onBashCompletion?: (completion: BashCompletedPayload, bridge: BinaryBridge) => void | Promise<void>;
|
|
26
|
+
}
|
|
27
|
+
export interface BashCompletedPayload extends BgCompletion {
|
|
28
|
+
type: "bash_completed";
|
|
29
|
+
session_id: string;
|
|
23
30
|
}
|
|
24
31
|
export interface BridgeRequestOptions {
|
|
25
32
|
onProgress?: (chunk: {
|
|
@@ -28,6 +35,23 @@ export interface BridgeRequestOptions {
|
|
|
28
35
|
}) => void;
|
|
29
36
|
/** Per-call transport timeout in milliseconds. Defaults to the bridge-wide timeout. */
|
|
30
37
|
transportTimeoutMs?: number;
|
|
38
|
+
/**
|
|
39
|
+
* Skip the "kill the child process on timeout" behavior for this request.
|
|
40
|
+
*
|
|
41
|
+
* The default (false) treats a transport-level timeout as evidence the bridge
|
|
42
|
+
* is wedged — Rust normally responds well within the budget, so silence past
|
|
43
|
+
* the deadline almost always means a stuck child. Killing forces a clean
|
|
44
|
+
* respawn on the next call.
|
|
45
|
+
*
|
|
46
|
+
* Some commands enforce their own timeouts on the Rust side (notably `bash`,
|
|
47
|
+
* which uses a watchdog thread to terminate the child shell and return a
|
|
48
|
+
* timeout response). For those, a transport timeout means the response was
|
|
49
|
+
* lost or queued behind something else — the bridge itself is still healthy
|
|
50
|
+
* and should keep its warm state (LSP servers, semantic index, callers
|
|
51
|
+
* cache, undo history). Pass `keepBridgeOnTimeout: true` to reject the
|
|
52
|
+
* request without tearing down the bridge.
|
|
53
|
+
*/
|
|
54
|
+
keepBridgeOnTimeout?: boolean;
|
|
31
55
|
}
|
|
32
56
|
interface SendOptions extends BridgeRequestOptions {
|
|
33
57
|
timeoutMs?: number;
|
|
@@ -60,12 +84,14 @@ export declare class BinaryBridge {
|
|
|
60
84
|
private minVersion;
|
|
61
85
|
private onVersionMismatch;
|
|
62
86
|
private onConfigureWarnings;
|
|
87
|
+
private onBashCompletion;
|
|
63
88
|
private restartResetTimer;
|
|
64
89
|
constructor(binaryPath: string, cwd: string, options?: BridgeOptions, configOverrides?: Record<string, unknown>);
|
|
65
90
|
/** Number of times the binary has been restarted after a crash. */
|
|
66
91
|
get restartCount(): number;
|
|
67
92
|
/** Whether the child process is currently alive. */
|
|
68
93
|
isAlive(): boolean;
|
|
94
|
+
hasPendingRequests(): boolean;
|
|
69
95
|
/**
|
|
70
96
|
* Send a command to the binary and return the parsed response.
|
|
71
97
|
* Lazy-spawns the binary on first call.
|
package/dist/bridge.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"bridge.d.ts","sourceRoot":"","sources":["../src/bridge.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"bridge.d.ts","sourceRoot":"","sources":["../src/bridge.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,uBAAuB,CAAC;AAiB1D;;;GAGG;AACH,wBAAgB,aAAa,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,GAAG,MAAM,CAkC1D;AA6CD,UAAU,wBAAwB;IAChC,WAAW,EAAE,MAAM,CAAC;IACpB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,QAAQ,EAAE,OAAO,EAAE,CAAC;CACrB;AAED,MAAM,WAAW,aAAa;IAC5B,sDAAsD;IACtD,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,4DAA4D;IAC5D,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,qGAAqG;IACrG,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,iGAAiG;IACjG,iBAAiB,CAAC,EAAE,CAAC,aAAa,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,KAAK,IAAI,CAAC;IACxE,iFAAiF;IACjF,mBAAmB,CAAC,EAAE,CAAC,OAAO,EAAE,wBAAwB,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAClF,4DAA4D;IAC5D,gBAAgB,CAAC,EAAE,CACjB,UAAU,EAAE,oBAAoB,EAChC,MAAM,EAAE,YAAY,KACjB,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CAC3B;AAED,MAAM,WAAW,oBAAqB,SAAQ,YAAY;IACxD,IAAI,EAAE,gBAAgB,CAAC;IACvB,UAAU,EAAE,MAAM,CAAC;CACpB;AAED,MAAM,WAAW,oBAAoB;IACnC,UAAU,CAAC,EAAE,CAAC,KAAK,EAAE;QAAE,IAAI,EAAE,QAAQ,GAAG,QAAQ,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,KAAK,IAAI,CAAC;IAC1E,uFAAuF;IACvF,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B;;;;;;;;;;;;;;;OAeG;IACH,mBAAmB,CAAC,EAAE,OAAO,CAAC;CAC/B;AAED,UAAU,WAAY,SAAQ,oBAAoB;IAChD,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,sBAAsB,CAAC,EAAE,OAAO,CAAC;CAClC;AAED;;;;GAIG;AACH,qBAAa,YAAY;IACvB,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,gBAAgB,CAAiB;IACzD,kEAAkE;IAClE,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,eAAe,CAAM;IAE7C,OAAO,CAAC,UAAU,CAAS;IAC3B,OAAO,CAAC,GAAG,CAAS;IACpB,OAAO,CAAC,OAAO,CAA6B;IAC5C,OAAO,CAAC,OAAO,CAAqC;IACpD,OAAO,CAAC,MAAM,CAAK;IACnB,OAAO,CAAC,YAAY,CAAM;IAC1B,sEAAsE;IACtE,OAAO,CAAC,UAAU,CAAgB;IAClC,OAAO,CAAC,aAAa,CAAK;IAC1B,OAAO,CAAC,aAAa,CAAS;IAC9B,OAAO,CAAC,SAAS,CAAS;IAC1B,OAAO,CAAC,WAAW,CAAS;IAC5B,OAAO,CAAC,UAAU,CAAS;IAC3B,OAAO,CAAC,iBAAiB,CAA8B;IACvD,OAAO,CAAC,eAAe,CAA0B;IACjD,OAAO,CAAC,UAAU,CAAqB;IACvC,OAAO,CAAC,iBAAiB,CAAoE;IAC7F,OAAO,CAAC,mBAAmB,CAEb;IACd,OAAO,CAAC,gBAAgB,CAEV;IACd,OAAO,CAAC,iBAAiB,CAA8C;gBAGrE,UAAU,EAAE,MAAM,EAClB,GAAG,EAAE,MAAM,EACX,OAAO,CAAC,EAAE,aAAa,EACvB,eAAe,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC;IAa3C,mEAAmE;IACnE,IAAI,YAAY,IAAI,MAAM,CAEzB;IAED,oDAAoD;IACpD,OAAO,IAAI,OAAO;IAIlB,kBAAkB,IAAI,OAAO;IAI7B;;;OAGG;IACG,IAAI,CACR,OAAO,EAAE,MAAM,EACf,MAAM,GAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAM,EACpC,OAAO,CAAC,EAAE,WAAW,GACpB,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;YA6IrB,wBAAwB;IAuBtC,8DAA8D;IACxD,QAAQ,IAAI,OAAO,CAAC,IAAI,CAAC;IA4B/B,gGAAgG;YAClF,YAAY;IAoB1B,OAAO,CAAC,aAAa;IAKrB,OAAO,CAAC,YAAY;IAkHpB,OAAO,CAAC,cAAc;IAOtB;;;;OAIG;IACH,OAAO,CAAC,gBAAgB;IAMxB,OAAO,CAAC,YAAY;IA+DpB,OAAO,CAAC,aAAa;IAmCrB,OAAO,CAAC,WAAW;IAoDnB,OAAO,CAAC,gBAAgB;IAQxB,OAAO,CAAC,yBAAyB;IAQjC,OAAO,CAAC,sBAAsB;CAM/B"}
|
package/dist/config.d.ts
CHANGED
|
@@ -11,6 +11,7 @@ export declare const LspServerSchema: z.ZodObject<{
|
|
|
11
11
|
}, z.core.$strip>;
|
|
12
12
|
export declare const AftConfigSchema: z.ZodObject<{
|
|
13
13
|
format_on_edit: z.ZodOptional<z.ZodBoolean>;
|
|
14
|
+
formatter_timeout_secs: z.ZodOptional<z.ZodNumber>;
|
|
14
15
|
validate_on_edit: z.ZodOptional<z.ZodEnum<{
|
|
15
16
|
syntax: "syntax";
|
|
16
17
|
full: "full";
|
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;AAmExB,eAAO,MAAM,eAAe;;;;;;;;;iBAE1B,CAAC;AA2CH,eAAO,MAAM,eAAe
|
|
1
|
+
{"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAIA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAmExB,eAAO,MAAM,eAAe;;;;;;;;;iBAE1B,CAAC;AA2CH,eAAO,MAAM,eAAe;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;kBAkEjB,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,UAAU,EAAE,MAAM,EAAE,CAAC;IACrB,MAAM,EAAE,MAAM,CAAC;IACf,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,mBAAmB,CAAC,EAAE,OAAO,CAAC;CAC/B;AAMD,wBAAgB,4BAA4B,CAAC,MAAM,EAAE,SAAS,GAAG,qBAAqB,CAmDrF;AAED,wBAAgB,qCAAqC,CACnD,MAAM,EAAE,SAAS,GAChB,8BAA8B,CAehC;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;AAiGF,wBAAgB,oBAAoB,CAClC,UAAU,EAAE,MAAM,EAClB,MAAM,GAAE,MAAsB,GAC7B;IAAE,QAAQ,EAAE,OAAO,CAAC;IAAC,OAAO,EAAE,MAAM,EAAE,CAAA;CAAE,CAwD1C;AAuTD;;;;;;;;;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":"AAIA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,qBAAqB,CAAC;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,qBAAqB,CAAC;AAgKlD;;;;;;;;;;;;;;;;;;GAkBG;AACH,QAAA,MAAM,MAAM,EAAE,MAkiBb,CAAC;AAEF,eAAe,MAAM,CAAC"}
|
package/dist/index.js
CHANGED
|
@@ -7813,7 +7813,8 @@ import * as fs from "fs";
|
|
|
7813
7813
|
import * as os from "os";
|
|
7814
7814
|
import * as path from "path";
|
|
7815
7815
|
var TAG = "[aft-plugin]";
|
|
7816
|
-
var
|
|
7816
|
+
var isTestEnv = process.env.BUN_TEST === "1" || false;
|
|
7817
|
+
var logFile = path.join(os.tmpdir(), isTestEnv ? "aft-plugin-test.log" : "aft-plugin.log");
|
|
7817
7818
|
var useStderr = process.env.AFT_LOG_STDERR === "1";
|
|
7818
7819
|
var buffer = [];
|
|
7819
7820
|
var flushTimer = null;
|
|
@@ -7871,6 +7872,9 @@ function warn(message, data) {
|
|
|
7871
7872
|
function error(message, data) {
|
|
7872
7873
|
write("ERROR", message, data);
|
|
7873
7874
|
}
|
|
7875
|
+
function sessionLog(sessionId, message, data) {
|
|
7876
|
+
write("INFO", message, data, sessionId);
|
|
7877
|
+
}
|
|
7874
7878
|
function sessionWarn(sessionId, message, data) {
|
|
7875
7879
|
write("WARN", message, data, sessionId);
|
|
7876
7880
|
}
|
|
@@ -7878,15 +7882,90 @@ function getLogFilePath() {
|
|
|
7878
7882
|
return logFile;
|
|
7879
7883
|
}
|
|
7880
7884
|
|
|
7885
|
+
// src/shared/last-user-model.ts
|
|
7886
|
+
var CACHE_TTL_MS = 5000;
|
|
7887
|
+
var CACHE_MAX_ENTRIES = 100;
|
|
7888
|
+
var cache = new Map;
|
|
7889
|
+
async function getLastUserModel(client, sessionId) {
|
|
7890
|
+
const now = Date.now();
|
|
7891
|
+
const cached = cache.get(sessionId);
|
|
7892
|
+
if (cached && cached.expiresAt > now) {
|
|
7893
|
+
cache.delete(sessionId);
|
|
7894
|
+
cache.set(sessionId, cached);
|
|
7895
|
+
return cached.model;
|
|
7896
|
+
}
|
|
7897
|
+
if (cached)
|
|
7898
|
+
cache.delete(sessionId);
|
|
7899
|
+
const c = client;
|
|
7900
|
+
const fetcher = c?.session?.messages;
|
|
7901
|
+
if (typeof fetcher !== "function") {
|
|
7902
|
+
return null;
|
|
7903
|
+
}
|
|
7904
|
+
let messages;
|
|
7905
|
+
try {
|
|
7906
|
+
const result = await fetcher({ path: { id: sessionId } });
|
|
7907
|
+
messages = result?.data ?? [];
|
|
7908
|
+
} catch {
|
|
7909
|
+
return null;
|
|
7910
|
+
}
|
|
7911
|
+
for (let i = messages.length - 1;i >= 0; i--) {
|
|
7912
|
+
const info = messages[i]?.info;
|
|
7913
|
+
if (info?.role !== "user")
|
|
7914
|
+
continue;
|
|
7915
|
+
if (isSyntheticIgnoredMessage(messages[i]))
|
|
7916
|
+
continue;
|
|
7917
|
+
const model = info.model;
|
|
7918
|
+
if (!model?.providerID || !model?.modelID)
|
|
7919
|
+
continue;
|
|
7920
|
+
const resolved = {
|
|
7921
|
+
providerID: model.providerID,
|
|
7922
|
+
modelID: model.modelID,
|
|
7923
|
+
...model.variant ? { variant: model.variant } : {}
|
|
7924
|
+
};
|
|
7925
|
+
setCache(sessionId, resolved);
|
|
7926
|
+
return resolved;
|
|
7927
|
+
}
|
|
7928
|
+
return null;
|
|
7929
|
+
}
|
|
7930
|
+
function setCache(sessionId, model) {
|
|
7931
|
+
if (cache.has(sessionId))
|
|
7932
|
+
cache.delete(sessionId);
|
|
7933
|
+
cache.set(sessionId, { expiresAt: Date.now() + CACHE_TTL_MS, model });
|
|
7934
|
+
while (cache.size > CACHE_MAX_ENTRIES) {
|
|
7935
|
+
const oldest = cache.keys().next().value;
|
|
7936
|
+
if (typeof oldest !== "string")
|
|
7937
|
+
break;
|
|
7938
|
+
cache.delete(oldest);
|
|
7939
|
+
}
|
|
7940
|
+
}
|
|
7941
|
+
function isSyntheticIgnoredMessage(message) {
|
|
7942
|
+
const parts = message?.info?.parts ?? message?.parts;
|
|
7943
|
+
return Array.isArray(parts) && parts.length > 0 && parts.every((part) => part?.ignored === true);
|
|
7944
|
+
}
|
|
7945
|
+
|
|
7881
7946
|
// src/bg-notifications.ts
|
|
7882
7947
|
var sessionBgStates = new Map;
|
|
7883
7948
|
var SESSION_BG_STATE_IDLE_TTL_MS = 60 * 60 * 1000;
|
|
7884
7949
|
var DEBOUNCE_STEP_MS = 200;
|
|
7885
7950
|
var DEBOUNCE_CAP_MS = 1000;
|
|
7951
|
+
var UNKNOWN_COMPLETION_TTL_MS = 5000;
|
|
7952
|
+
var UNKNOWN_COMPLETION_CAP = 32;
|
|
7886
7953
|
var DEFAULT_SESSION_ID = "__default__";
|
|
7887
7954
|
var LOG_PREFIX = "[aft-plugin] bg-notifications:";
|
|
7888
7955
|
function trackBgTask(sessionID, taskId) {
|
|
7889
|
-
stateFor(sessionID)
|
|
7956
|
+
const state = stateFor(sessionID);
|
|
7957
|
+
pruneUnknownCompletions(state, Date.now());
|
|
7958
|
+
const buffered = state.unknownCompletions.filter((entry) => entry.completion.task_id === taskId);
|
|
7959
|
+
state.unknownCompletions = state.unknownCompletions.filter((entry) => entry.completion.task_id !== taskId);
|
|
7960
|
+
if (buffered.length > 0) {
|
|
7961
|
+
for (const entry of buffered) {
|
|
7962
|
+
if (!state.pendingCompletions.some((pending) => pending.task_id === taskId)) {
|
|
7963
|
+
state.pendingCompletions.push(entry.completion);
|
|
7964
|
+
}
|
|
7965
|
+
}
|
|
7966
|
+
return;
|
|
7967
|
+
}
|
|
7968
|
+
state.outstandingTaskIds.add(taskId);
|
|
7890
7969
|
}
|
|
7891
7970
|
function ingestBgCompletions(sessionID, completions) {
|
|
7892
7971
|
if (!Array.isArray(completions) || completions.length === 0)
|
|
@@ -7896,8 +7975,10 @@ function ingestBgCompletions(sessionID, completions) {
|
|
|
7896
7975
|
for (const completion of completions) {
|
|
7897
7976
|
if (!isBgCompletion(completion))
|
|
7898
7977
|
continue;
|
|
7899
|
-
if (!state.outstandingTaskIds.has(completion.task_id))
|
|
7978
|
+
if (!state.outstandingTaskIds.has(completion.task_id)) {
|
|
7979
|
+
bufferUnknownCompletion(state, completion);
|
|
7900
7980
|
continue;
|
|
7981
|
+
}
|
|
7901
7982
|
state.outstandingTaskIds.delete(completion.task_id);
|
|
7902
7983
|
if (!state.pendingCompletions.some((pending) => pending.task_id === completion.task_id) && !accepted.some((pending) => pending.task_id === completion.task_id)) {
|
|
7903
7984
|
accepted.push(completion);
|
|
@@ -7906,6 +7987,10 @@ function ingestBgCompletions(sessionID, completions) {
|
|
|
7906
7987
|
state.pendingCompletions.push(...accepted);
|
|
7907
7988
|
return accepted;
|
|
7908
7989
|
}
|
|
7990
|
+
async function handlePushedBgCompletion(drainContext, completion) {
|
|
7991
|
+
ingestBgCompletions(drainContext.sessionID, [completion]);
|
|
7992
|
+
await triggerWakeIfPending(drainContext, true);
|
|
7993
|
+
}
|
|
7909
7994
|
async function appendInTurnBgCompletions(drainContext, output) {
|
|
7910
7995
|
if (!output)
|
|
7911
7996
|
return;
|
|
@@ -7922,43 +8007,55 @@ async function appendInTurnBgCompletions(drainContext, output) {
|
|
|
7922
8007
|
state.pendingCompletions = [];
|
|
7923
8008
|
}
|
|
7924
8009
|
async function handleIdleBgCompletions(drainContext) {
|
|
8010
|
+
await triggerWakeIfPending(drainContext, false);
|
|
8011
|
+
}
|
|
8012
|
+
async function triggerWakeIfPending(drainContext, skipDrain) {
|
|
7925
8013
|
const state = stateFor(drainContext.sessionID);
|
|
7926
8014
|
if (state.wakeFiredThisIdle)
|
|
7927
8015
|
return;
|
|
7928
|
-
if (
|
|
8016
|
+
if (drainContext.isActive?.())
|
|
8017
|
+
return;
|
|
8018
|
+
if (!skipDrain && state.outstandingTaskIds.size > 0) {
|
|
7929
8019
|
await drainCompletions(drainContext);
|
|
7930
8020
|
}
|
|
7931
8021
|
if (state.pendingCompletions.length === 0)
|
|
7932
8022
|
return;
|
|
7933
8023
|
scheduleWake(state, async (reminder) => {
|
|
7934
|
-
|
|
7935
|
-
|
|
7936
|
-
|
|
7937
|
-
|
|
7938
|
-
|
|
7939
|
-
|
|
7940
|
-
|
|
7941
|
-
|
|
7942
|
-
|
|
7943
|
-
|
|
7944
|
-
|
|
7945
|
-
|
|
7946
|
-
|
|
7947
|
-
|
|
7948
|
-
|
|
8024
|
+
const client = drainContext.client;
|
|
8025
|
+
if (typeof client.session?.promptAsync !== "function") {
|
|
8026
|
+
throw new Error("client.session.promptAsync is unavailable");
|
|
8027
|
+
}
|
|
8028
|
+
const lastModel = await getLastUserModel(client, drainContext.sessionID);
|
|
8029
|
+
const body = {
|
|
8030
|
+
noReply: false,
|
|
8031
|
+
parts: [{ type: "text", text: reminder }]
|
|
8032
|
+
};
|
|
8033
|
+
if (lastModel) {
|
|
8034
|
+
body.model = { providerID: lastModel.providerID, modelID: lastModel.modelID };
|
|
8035
|
+
if (lastModel.variant)
|
|
8036
|
+
body.variant = lastModel.variant;
|
|
8037
|
+
}
|
|
8038
|
+
await client.session.promptAsync({
|
|
8039
|
+
path: { id: drainContext.sessionID },
|
|
8040
|
+
body
|
|
8041
|
+
});
|
|
8042
|
+
}, (err) => {
|
|
8043
|
+
sessionWarn(drainContext.sessionID, `${LOG_PREFIX} wake send failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
7949
8044
|
});
|
|
7950
8045
|
}
|
|
7951
8046
|
function resetBgWake(sessionID) {
|
|
7952
8047
|
stateFor(sessionID).wakeFiredThisIdle = false;
|
|
7953
8048
|
}
|
|
7954
8049
|
function formatSystemReminder(completions) {
|
|
7955
|
-
const bullets = completions.map((completion) =>
|
|
8050
|
+
const bullets = completions.map((completion) => formatCompletion(completion)).join(`
|
|
7956
8051
|
`);
|
|
8052
|
+
const anyTruncated = completions.some((c) => c.output_truncated === true);
|
|
8053
|
+
const tail = anyTruncated ? `
|
|
8054
|
+
|
|
8055
|
+
For truncated tasks, use bash_status({ taskId: "..." }) to retrieve full output.` : "";
|
|
7957
8056
|
return `<system-reminder>
|
|
7958
8057
|
[BACKGROUND BASH COMPLETED]
|
|
7959
|
-
${bullets}
|
|
7960
|
-
|
|
7961
|
-
Use bash_status({ task_id: "..." }) to retrieve full output.
|
|
8058
|
+
${bullets}${tail}
|
|
7962
8059
|
</system-reminder>`;
|
|
7963
8060
|
}
|
|
7964
8061
|
function extractSessionID(value) {
|
|
@@ -7984,15 +8081,15 @@ async function drainCompletions({ ctx, directory, sessionID }) {
|
|
|
7984
8081
|
const bridge = ctx.pool.getAnyActiveBridge(directory) ?? ctx.pool.getBridge(directory);
|
|
7985
8082
|
const response = await bridge.send("bash_drain_completions", { session_id: sessionID });
|
|
7986
8083
|
if (response.success === false) {
|
|
7987
|
-
|
|
8084
|
+
sessionWarn(sessionID, `${LOG_PREFIX} drain failed: ${String(response.message ?? "unknown error")}`);
|
|
7988
8085
|
return;
|
|
7989
8086
|
}
|
|
7990
8087
|
ingestBgCompletions(sessionID, response.bg_completions);
|
|
7991
8088
|
} catch (err) {
|
|
7992
|
-
|
|
8089
|
+
sessionWarn(sessionID, `${LOG_PREFIX} drain failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
7993
8090
|
}
|
|
7994
8091
|
}
|
|
7995
|
-
function scheduleWake(state, sendWake) {
|
|
8092
|
+
function scheduleWake(state, sendWake, onSendFailure) {
|
|
7996
8093
|
const now = Date.now();
|
|
7997
8094
|
if (state.debounceTimer && state.pendingCompletions.length <= state.scheduledCompletionCount) {
|
|
7998
8095
|
return;
|
|
@@ -8007,16 +8104,24 @@ function scheduleWake(state, sendWake) {
|
|
|
8007
8104
|
state.scheduledCompletionCount = state.pendingCompletions.length;
|
|
8008
8105
|
if (state.debounceTimer)
|
|
8009
8106
|
clearTimeout(state.debounceTimer);
|
|
8010
|
-
const delay = Math.max(0, (state.scheduledFireAt ?? now) - now);
|
|
8107
|
+
const delay = state.retryDelayMs ?? Math.max(0, (state.scheduledFireAt ?? now) - now);
|
|
8011
8108
|
state.debounceTimer = setTimeout(() => {
|
|
8012
|
-
const
|
|
8109
|
+
const pending = state.pendingCompletions;
|
|
8110
|
+
const reminder = formatSystemReminder(pending);
|
|
8013
8111
|
state.pendingCompletions = [];
|
|
8014
8112
|
state.debounceTimer = null;
|
|
8015
|
-
state.wakeFiredThisIdle = true;
|
|
8016
8113
|
state.firstCompletionAt = null;
|
|
8017
8114
|
state.scheduledFireAt = null;
|
|
8018
8115
|
state.scheduledCompletionCount = 0;
|
|
8019
|
-
sendWake(reminder)
|
|
8116
|
+
sendWake(reminder).then(() => {
|
|
8117
|
+
state.retryDelayMs = null;
|
|
8118
|
+
state.wakeFiredThisIdle = true;
|
|
8119
|
+
}).catch((err) => {
|
|
8120
|
+
state.pendingCompletions = [...pending, ...state.pendingCompletions];
|
|
8121
|
+
state.retryDelayMs = Math.min((delay || DEBOUNCE_STEP_MS) * 2, DEBOUNCE_CAP_MS);
|
|
8122
|
+
onSendFailure(err);
|
|
8123
|
+
scheduleWake(state, sendWake, onSendFailure);
|
|
8124
|
+
});
|
|
8020
8125
|
}, delay);
|
|
8021
8126
|
state.debounceTimer.unref?.();
|
|
8022
8127
|
}
|
|
@@ -8034,6 +8139,8 @@ function stateFor(sessionID) {
|
|
|
8034
8139
|
firstCompletionAt: null,
|
|
8035
8140
|
scheduledFireAt: null,
|
|
8036
8141
|
scheduledCompletionCount: 0,
|
|
8142
|
+
retryDelayMs: null,
|
|
8143
|
+
unknownCompletions: [],
|
|
8037
8144
|
lastSeenAt: now
|
|
8038
8145
|
};
|
|
8039
8146
|
sessionBgStates.set(key, state);
|
|
@@ -8054,6 +8161,18 @@ function cleanupIdleSessionStates(now) {
|
|
|
8054
8161
|
sessionBgStates.delete(sessionID);
|
|
8055
8162
|
}
|
|
8056
8163
|
}
|
|
8164
|
+
function bufferUnknownCompletion(state, completion) {
|
|
8165
|
+
const now = Date.now();
|
|
8166
|
+
pruneUnknownCompletions(state, now);
|
|
8167
|
+
state.unknownCompletions = state.unknownCompletions.filter((entry) => entry.completion.task_id !== completion.task_id);
|
|
8168
|
+
state.unknownCompletions.push({ completion, receivedAt: now });
|
|
8169
|
+
if (state.unknownCompletions.length > UNKNOWN_COMPLETION_CAP) {
|
|
8170
|
+
state.unknownCompletions.splice(0, state.unknownCompletions.length - UNKNOWN_COMPLETION_CAP);
|
|
8171
|
+
}
|
|
8172
|
+
}
|
|
8173
|
+
function pruneUnknownCompletions(state, now) {
|
|
8174
|
+
state.unknownCompletions = state.unknownCompletions.filter((entry) => now - entry.receivedAt <= UNKNOWN_COMPLETION_TTL_MS);
|
|
8175
|
+
}
|
|
8057
8176
|
function isBgCompletion(value) {
|
|
8058
8177
|
if (!value || typeof value !== "object" || Array.isArray(value))
|
|
8059
8178
|
return false;
|
|
@@ -8068,10 +8187,26 @@ ${reminder}` : reminder;
|
|
|
8068
8187
|
function formatCompletion(completion) {
|
|
8069
8188
|
const status = formatStatus(completion);
|
|
8070
8189
|
const duration = formatDuration(completion);
|
|
8071
|
-
|
|
8190
|
+
const header = `- task ${completion.task_id} (${status}${duration ? `, ${duration}` : ""})`;
|
|
8191
|
+
const previewBlock = formatOutputPreview(completion);
|
|
8192
|
+
return previewBlock ? `${header}
|
|
8193
|
+
${previewBlock}` : header;
|
|
8194
|
+
}
|
|
8195
|
+
function formatOutputPreview(completion) {
|
|
8196
|
+
const ansiRegex = /\x1b\[[0-9;]*[a-zA-Z]/g;
|
|
8197
|
+
const raw = (completion.output_preview ?? "").replace(ansiRegex, "");
|
|
8198
|
+
if (!raw.trim())
|
|
8199
|
+
return "";
|
|
8200
|
+
const trimmed = raw.replace(/\n+$/, "");
|
|
8201
|
+
const ellipsis = completion.output_truncated ? "\u2026" : "";
|
|
8202
|
+
const indented = trimmed.split(`
|
|
8203
|
+
`).map((line) => ` ${line}`).join(`
|
|
8204
|
+
`);
|
|
8205
|
+
return ellipsis ? ` ${ellipsis}
|
|
8206
|
+
${indented}` : indented;
|
|
8072
8207
|
}
|
|
8073
8208
|
function formatStatus(completion) {
|
|
8074
|
-
if (completion.status === "timeout")
|
|
8209
|
+
if (completion.status === "timed_out" || completion.status === "timeout")
|
|
8075
8210
|
return "timed out";
|
|
8076
8211
|
if (completion.status === "killed")
|
|
8077
8212
|
return "killed";
|
|
@@ -21693,6 +21828,7 @@ var ExperimentalConfigSchema = exports_external.object({
|
|
|
21693
21828
|
});
|
|
21694
21829
|
var AftConfigSchema = exports_external.object({
|
|
21695
21830
|
format_on_edit: exports_external.boolean().optional(),
|
|
21831
|
+
formatter_timeout_secs: exports_external.number().int().min(1).max(600).optional(),
|
|
21696
21832
|
validate_on_edit: exports_external.enum(["syntax", "full"]).optional(),
|
|
21697
21833
|
formatter: exports_external.record(exports_external.string(), FormatterEnum).optional(),
|
|
21698
21834
|
checker: exports_external.record(exports_external.string(), CheckerEnum).optional(),
|
|
@@ -24332,13 +24468,17 @@ async function getSessionMessages(client, sessionId) {
|
|
|
24332
24468
|
async function sendIgnoredMessage(client, sessionId, text) {
|
|
24333
24469
|
try {
|
|
24334
24470
|
const c = client;
|
|
24335
|
-
const
|
|
24336
|
-
|
|
24337
|
-
|
|
24338
|
-
|
|
24339
|
-
parts: [{ type: "text", text, ignored: true }]
|
|
24340
|
-
}
|
|
24471
|
+
const lastModel = await getLastUserModel(client, sessionId);
|
|
24472
|
+
const body = {
|
|
24473
|
+
noReply: true,
|
|
24474
|
+
parts: [{ type: "text", text, ignored: true }]
|
|
24341
24475
|
};
|
|
24476
|
+
if (lastModel) {
|
|
24477
|
+
body.model = { providerID: lastModel.providerID, modelID: lastModel.modelID };
|
|
24478
|
+
if (lastModel.variant)
|
|
24479
|
+
body.variant = lastModel.variant;
|
|
24480
|
+
}
|
|
24481
|
+
const promptInput = { path: { id: sessionId }, body };
|
|
24342
24482
|
if (typeof c.session?.prompt === "function") {
|
|
24343
24483
|
await Promise.resolve(c.session.prompt(promptInput));
|
|
24344
24484
|
return true;
|
|
@@ -24348,7 +24488,7 @@ async function sendIgnoredMessage(client, sessionId, text) {
|
|
|
24348
24488
|
return true;
|
|
24349
24489
|
}
|
|
24350
24490
|
} catch (err) {
|
|
24351
|
-
|
|
24491
|
+
sessionLog(sessionId, `[aft-plugin] notification send failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
24352
24492
|
}
|
|
24353
24493
|
return false;
|
|
24354
24494
|
}
|
|
@@ -24374,7 +24514,7 @@ async function sendWarning(opts, message) {
|
|
|
24374
24514
|
if (!sessionId)
|
|
24375
24515
|
return;
|
|
24376
24516
|
const text = `${WARNING_MARKER} ${message}`;
|
|
24377
|
-
|
|
24517
|
+
sessionLog(sessionId, `[aft-plugin] sending warning to session ${sessionId}`);
|
|
24378
24518
|
await sendIgnoredMessage(opts.client, sessionId, text);
|
|
24379
24519
|
}
|
|
24380
24520
|
async function sendFeatureAnnouncement(opts, version2, features, storageDir) {
|
|
@@ -24397,7 +24537,7 @@ async function sendFeatureAnnouncement(opts, version2, features, storageDir) {
|
|
|
24397
24537
|
return;
|
|
24398
24538
|
const text = [`${FEATURE_MARKER} v${version2}:`, ...features.map((f) => ` \u2022 ${f}`)].join(`
|
|
24399
24539
|
`);
|
|
24400
|
-
|
|
24540
|
+
sessionLog(sessionId, `[aft-plugin] sending feature announcement for v${version2}`);
|
|
24401
24541
|
await sendIgnoredMessage(opts.client, sessionId, text);
|
|
24402
24542
|
}
|
|
24403
24543
|
if (storageDir) {
|
|
@@ -24515,7 +24655,7 @@ async function cleanupWarnings(opts) {
|
|
|
24515
24655
|
}
|
|
24516
24656
|
if (warningIds.length === 0)
|
|
24517
24657
|
return;
|
|
24518
|
-
|
|
24658
|
+
sessionLog(sessionId, `[aft-plugin] cleaning up ${warningIds.length} stale warning(s)`);
|
|
24519
24659
|
for (const id of warningIds) {
|
|
24520
24660
|
await deleteMessage(effectiveServerUrl, sessionId, id);
|
|
24521
24661
|
}
|
|
@@ -25060,6 +25200,7 @@ class BinaryBridge {
|
|
|
25060
25200
|
minVersion;
|
|
25061
25201
|
onVersionMismatch;
|
|
25062
25202
|
onConfigureWarnings;
|
|
25203
|
+
onBashCompletion;
|
|
25063
25204
|
restartResetTimer = null;
|
|
25064
25205
|
constructor(binaryPath, cwd, options, configOverrides) {
|
|
25065
25206
|
this.binaryPath = binaryPath;
|
|
@@ -25070,6 +25211,7 @@ class BinaryBridge {
|
|
|
25070
25211
|
this.minVersion = options?.minVersion;
|
|
25071
25212
|
this.onVersionMismatch = options?.onVersionMismatch;
|
|
25072
25213
|
this.onConfigureWarnings = options?.onConfigureWarnings;
|
|
25214
|
+
this.onBashCompletion = options?.onBashCompletion;
|
|
25073
25215
|
}
|
|
25074
25216
|
get restartCount() {
|
|
25075
25217
|
return this._restartCount;
|
|
@@ -25077,6 +25219,9 @@ class BinaryBridge {
|
|
|
25077
25219
|
isAlive() {
|
|
25078
25220
|
return this.process !== null && this.process.exitCode === null && !this.process.killed;
|
|
25079
25221
|
}
|
|
25222
|
+
hasPendingRequests() {
|
|
25223
|
+
return this.pending.size > 0;
|
|
25224
|
+
}
|
|
25080
25225
|
async send(command, params = {}, options) {
|
|
25081
25226
|
if (this._shuttingDown) {
|
|
25082
25227
|
throw new Error(`[aft-plugin] Bridge is shutting down, cannot send "${command}"`);
|
|
@@ -25130,17 +25275,21 @@ class BinaryBridge {
|
|
|
25130
25275
|
`;
|
|
25131
25276
|
const effectiveTimeoutMs = options?.transportTimeoutMs ?? options?.timeoutMs ?? this.timeoutMs;
|
|
25132
25277
|
const requestSessionId = typeof params.session_id === "string" && params.session_id.length > 0 ? params.session_id : undefined;
|
|
25278
|
+
const keepBridgeOnTimeout = options?.keepBridgeOnTimeout === true;
|
|
25133
25279
|
return new Promise((resolve4, reject) => {
|
|
25134
25280
|
const timer = setTimeout(() => {
|
|
25135
25281
|
this.pending.delete(id);
|
|
25136
|
-
const
|
|
25282
|
+
const restartSuffix = keepBridgeOnTimeout ? "" : " \u2014 restarting bridge";
|
|
25283
|
+
const timeoutMsg = `Request "${command}" (id=${id}) timed out after ${effectiveTimeoutMs}ms${restartSuffix}`;
|
|
25137
25284
|
if (requestSessionId) {
|
|
25138
25285
|
sessionWarn(requestSessionId, timeoutMsg);
|
|
25139
25286
|
} else {
|
|
25140
25287
|
warn(timeoutMsg);
|
|
25141
25288
|
}
|
|
25142
25289
|
reject(new Error(`[aft-plugin] Request "${command}" (id=${id}) timed out after ${effectiveTimeoutMs}ms`));
|
|
25143
|
-
|
|
25290
|
+
if (!keepBridgeOnTimeout) {
|
|
25291
|
+
this.handleTimeout();
|
|
25292
|
+
}
|
|
25144
25293
|
}, effectiveTimeoutMs);
|
|
25145
25294
|
this.pending.set(id, { resolve: resolve4, reject, timer, onProgress: options?.onProgress });
|
|
25146
25295
|
if (!this.process?.stdin?.writable) {
|
|
@@ -25345,6 +25494,10 @@ class BinaryBridge {
|
|
|
25345
25494
|
}
|
|
25346
25495
|
continue;
|
|
25347
25496
|
}
|
|
25497
|
+
if (response.type === "bash_completed") {
|
|
25498
|
+
this.onBashCompletion?.(response, this);
|
|
25499
|
+
continue;
|
|
25500
|
+
}
|
|
25348
25501
|
const id = response.id;
|
|
25349
25502
|
if (id && this.pending.has(id)) {
|
|
25350
25503
|
const entry = this.pending.get(id);
|
|
@@ -25354,6 +25507,8 @@ class BinaryBridge {
|
|
|
25354
25507
|
clearTimeout(entry.timer);
|
|
25355
25508
|
this.scheduleRestartCountReset();
|
|
25356
25509
|
entry.resolve(response);
|
|
25510
|
+
} else if (typeof response.type === "string") {
|
|
25511
|
+
log(`Ignoring unknown stdout push frame type: ${response.type}`);
|
|
25357
25512
|
}
|
|
25358
25513
|
} catch (_err) {
|
|
25359
25514
|
warn(`Failed to parse stdout line: ${line}`);
|
|
@@ -25451,7 +25606,8 @@ class BridgePool {
|
|
|
25451
25606
|
maxRestarts: options.maxRestarts,
|
|
25452
25607
|
minVersion: options.minVersion,
|
|
25453
25608
|
onVersionMismatch: options.onVersionMismatch,
|
|
25454
|
-
onConfigureWarnings: options.onConfigureWarnings
|
|
25609
|
+
onConfigureWarnings: options.onConfigureWarnings,
|
|
25610
|
+
onBashCompletion: options.onBashCompletion
|
|
25455
25611
|
};
|
|
25456
25612
|
this.configOverrides = configOverrides;
|
|
25457
25613
|
if (Number.isFinite(this.idleTimeoutMs)) {
|
|
@@ -25771,9 +25927,7 @@ class AftRpcServer {
|
|
|
25771
25927
|
return;
|
|
25772
25928
|
}
|
|
25773
25929
|
const { token: _token, ...handlerParams } = params;
|
|
25774
|
-
log(`RPC call: ${method} params=${JSON.stringify(handlerParams).slice(0, 200)}`);
|
|
25775
25930
|
handler(handlerParams).then((result) => {
|
|
25776
|
-
log(`RPC result: ${method} => ${JSON.stringify(result).slice(0, 200)}`);
|
|
25777
25931
|
res.writeHead(200, { "Content-Type": "application/json" });
|
|
25778
25932
|
res.end(JSON.stringify(result));
|
|
25779
25933
|
}).catch((err) => {
|
|
@@ -26026,7 +26180,7 @@ import {
|
|
|
26026
26180
|
import { isIP } from "net";
|
|
26027
26181
|
import { join as join17 } from "path";
|
|
26028
26182
|
var MAX_RESPONSE_BYTES = 10 * 1024 * 1024;
|
|
26029
|
-
var
|
|
26183
|
+
var CACHE_TTL_MS2 = 24 * 60 * 60 * 1000;
|
|
26030
26184
|
var FETCH_TIMEOUT_MS = 30000;
|
|
26031
26185
|
var MAX_REDIRECTS = 5;
|
|
26032
26186
|
function cacheDir(storageDir) {
|
|
@@ -26206,7 +26360,7 @@ async function fetchUrlToTempFile(url2, storageDir, options = {}) {
|
|
|
26206
26360
|
const meta4 = JSON.parse(readFileSync8(metaFile, "utf8"));
|
|
26207
26361
|
const age = Date.now() - meta4.fetchedAt;
|
|
26208
26362
|
const cached2 = contentPath(storageDir, hash2, meta4.extension);
|
|
26209
|
-
if (age <
|
|
26363
|
+
if (age < CACHE_TTL_MS2 && existsSync11(cached2)) {
|
|
26210
26364
|
log(`URL cache hit: ${url2} (${Math.round(age / 1000)}s old)`);
|
|
26211
26365
|
return cached2;
|
|
26212
26366
|
}
|
|
@@ -26279,7 +26433,7 @@ function cleanupUrlCache(storageDir) {
|
|
|
26279
26433
|
try {
|
|
26280
26434
|
const meta3 = JSON.parse(readFileSync8(metaFile, "utf8"));
|
|
26281
26435
|
const age = Date.now() - meta3.fetchedAt;
|
|
26282
|
-
if (age >
|
|
26436
|
+
if (age > CACHE_TTL_MS2) {
|
|
26283
26437
|
const hash2 = entry.slice(0, -".meta.json".length);
|
|
26284
26438
|
const content = contentPath(storageDir, hash2, meta3.extension);
|
|
26285
26439
|
if (existsSync11(content))
|
|
@@ -27079,6 +27233,7 @@ function createBashTool(ctx) {
|
|
|
27079
27233
|
permissions_requested: true
|
|
27080
27234
|
}, callBridge, {
|
|
27081
27235
|
transportTimeoutMs: bashTransportTimeoutMs(args.timeout),
|
|
27236
|
+
keepBridgeOnTimeout: true,
|
|
27082
27237
|
onProgress: ({ text }) => {
|
|
27083
27238
|
accumulatedOutput = preview(accumulatedOutput + text);
|
|
27084
27239
|
metadata?.({ output: accumulatedOutput, description });
|
|
@@ -27140,6 +27295,49 @@ function createBashTool(ctx) {
|
|
|
27140
27295
|
}
|
|
27141
27296
|
};
|
|
27142
27297
|
}
|
|
27298
|
+
function createBashStatusTool(ctx) {
|
|
27299
|
+
return {
|
|
27300
|
+
description: "Check the status and captured output of a background bash task spawned with bash({ background: true }). Returns status (running | completed | failed | killed | timed_out), exit code, duration, and a preview of captured output.",
|
|
27301
|
+
args: {
|
|
27302
|
+
taskId: z3.string().describe("Background task ID returned by bash({ background: true }), e.g. bgb-6b454047.")
|
|
27303
|
+
},
|
|
27304
|
+
execute: async (args, context) => {
|
|
27305
|
+
const data = await callBridge(ctx, context, "bash_status", {
|
|
27306
|
+
task_id: args.taskId
|
|
27307
|
+
});
|
|
27308
|
+
if (data.success === false) {
|
|
27309
|
+
throw new Error(data.message ?? "bash_status failed");
|
|
27310
|
+
}
|
|
27311
|
+
const status = data.status;
|
|
27312
|
+
const exit = typeof data.exit_code === "number" ? ` (exit ${data.exit_code})` : "";
|
|
27313
|
+
const dur = typeof data.duration_ms === "number" ? ` ${Math.round(data.duration_ms / 1000)}s` : "";
|
|
27314
|
+
let text = `Task ${args.taskId}: ${status}${exit}${dur}`;
|
|
27315
|
+
const preview = data.output_preview;
|
|
27316
|
+
if (preview && status !== "running") {
|
|
27317
|
+
text += `
|
|
27318
|
+
${preview.slice(0, 2000)}`;
|
|
27319
|
+
}
|
|
27320
|
+
return text;
|
|
27321
|
+
}
|
|
27322
|
+
};
|
|
27323
|
+
}
|
|
27324
|
+
function createBashKillTool(ctx) {
|
|
27325
|
+
return {
|
|
27326
|
+
description: "Terminate a running background bash task spawned with bash({ background: true }). Returns confirmation of kill or an error if the task already finished.",
|
|
27327
|
+
args: {
|
|
27328
|
+
taskId: z3.string().describe("Background task ID returned by bash({ background: true }), e.g. bgb-6b454047.")
|
|
27329
|
+
},
|
|
27330
|
+
execute: async (args, context) => {
|
|
27331
|
+
const data = await callBridge(ctx, context, "bash_kill", {
|
|
27332
|
+
task_id: args.taskId
|
|
27333
|
+
});
|
|
27334
|
+
if (data.success === false) {
|
|
27335
|
+
throw new Error(data.message ?? "bash_kill failed");
|
|
27336
|
+
}
|
|
27337
|
+
return `Task ${args.taskId}: ${String(data.status ?? "killed")}`;
|
|
27338
|
+
}
|
|
27339
|
+
};
|
|
27340
|
+
}
|
|
27143
27341
|
function bashTransportTimeoutMs(timeout) {
|
|
27144
27342
|
const bashTimeout = timeout ?? DEFAULT_BASH_TIMEOUT_MS;
|
|
27145
27343
|
return Math.max(30000, bashTimeout + BASH_TRANSPORT_TIMEOUT_OVERHEAD_MS);
|
|
@@ -27770,6 +27968,11 @@ function createEditTool(ctx, writeToolName = "write") {
|
|
|
27770
27968
|
}
|
|
27771
27969
|
}
|
|
27772
27970
|
let result = JSON.stringify(data);
|
|
27971
|
+
const globSkipNote = formatGlobSkipReasonsNote(data.format_skip_reasons);
|
|
27972
|
+
if (globSkipNote)
|
|
27973
|
+
result += `
|
|
27974
|
+
|
|
27975
|
+
${globSkipNote}`;
|
|
27773
27976
|
if (!args.dryRun) {
|
|
27774
27977
|
const diags = data.lsp_diagnostics;
|
|
27775
27978
|
if (diags && diags.length > 0) {
|
|
@@ -27800,6 +28003,14 @@ Note: LSP server(s) exited during this edit: ${exitedServers.join(", ")}. Their
|
|
|
27800
28003
|
}
|
|
27801
28004
|
};
|
|
27802
28005
|
}
|
|
28006
|
+
function formatGlobSkipReasonsNote(reasons) {
|
|
28007
|
+
if (!Array.isArray(reasons))
|
|
28008
|
+
return;
|
|
28009
|
+
const actionable = reasons.filter((reason) => typeof reason === "string").filter((reason) => ["formatter_not_installed", "formatter_excluded_path", "timeout", "error"].includes(reason));
|
|
28010
|
+
if (actionable.length === 0)
|
|
28011
|
+
return;
|
|
28012
|
+
return `Note: formatter skipped some glob edit result file(s): ${[...new Set(actionable)].sort().join(", ")}. See per-file format_skipped_reason values for details.`;
|
|
28013
|
+
}
|
|
27803
28014
|
var APPLY_PATCH_DESCRIPTION = `Use the \`apply_patch\` tool to edit files. Your patch language is a stripped\u2011down, file\u2011oriented diff format designed to be easy to parse and safe to apply. You can think of it as a high\u2011level envelope:
|
|
27804
28015
|
|
|
27805
28016
|
*** Begin Patch
|
|
@@ -28051,8 +28262,8 @@ ${diagLines}`);
|
|
|
28051
28262
|
const summary = partial2 ? `Patch partially applied \u2014 ${hunks.length - failures.length} of ${hunks.length} hunk(s) succeeded. Failed: ${failures.join(", ")}. Successful changes are kept; use \`aft_safety\` to revert if you want to abort.` : `Patch failed \u2014 none of the ${hunks.length} hunk(s) applied: ${failures.join(", ")}.`;
|
|
28052
28263
|
results.push(summary);
|
|
28053
28264
|
if (!partial2) {
|
|
28054
|
-
|
|
28055
|
-
`);
|
|
28265
|
+
throw new Error(results.join(`
|
|
28266
|
+
`));
|
|
28056
28267
|
}
|
|
28057
28268
|
}
|
|
28058
28269
|
const callID = getCallID2(context);
|
|
@@ -28158,6 +28369,8 @@ function createMoveTool(ctx) {
|
|
|
28158
28369
|
function hoistedTools(ctx) {
|
|
28159
28370
|
return {
|
|
28160
28371
|
bash: createBashTool(ctx),
|
|
28372
|
+
bash_status: createBashStatusTool(ctx),
|
|
28373
|
+
bash_kill: createBashKillTool(ctx),
|
|
28161
28374
|
read: createReadTool(ctx),
|
|
28162
28375
|
write: createWriteTool(ctx, "edit"),
|
|
28163
28376
|
edit: createEditTool(ctx, "write"),
|
|
@@ -28622,7 +28835,7 @@ var LSP_SYMBOL_KIND_MAP = {
|
|
|
28622
28835
|
12: "function",
|
|
28623
28836
|
23: "struct"
|
|
28624
28837
|
};
|
|
28625
|
-
async function queryLspHints(client, symbolName, directory) {
|
|
28838
|
+
async function queryLspHints(client, symbolName, directory, sessionId) {
|
|
28626
28839
|
try {
|
|
28627
28840
|
const statusResult = await client.lsp.status();
|
|
28628
28841
|
const servers = statusResult.data;
|
|
@@ -28664,7 +28877,7 @@ async function queryLspHints(client, symbolName, directory) {
|
|
|
28664
28877
|
}
|
|
28665
28878
|
return { symbols: hints };
|
|
28666
28879
|
} catch (err) {
|
|
28667
|
-
|
|
28880
|
+
sessionWarn(sessionId, `LSP query failed for "${symbolName}": ${err.message}`);
|
|
28668
28881
|
return;
|
|
28669
28882
|
}
|
|
28670
28883
|
}
|
|
@@ -28753,7 +28966,7 @@ function refactoringTools(ctx) {
|
|
|
28753
28966
|
params.call_site_line = Number(args.callSiteLine);
|
|
28754
28967
|
break;
|
|
28755
28968
|
}
|
|
28756
|
-
const hints = await queryLspHints(ctx.client, args.symbol ?? args.name);
|
|
28969
|
+
const hints = await queryLspHints(ctx.client, args.symbol ?? args.name, undefined, context.sessionID);
|
|
28757
28970
|
if (hints)
|
|
28758
28971
|
params.lsp_hints = hints;
|
|
28759
28972
|
const response = await callBridge(ctx, context, commandMap[op], params);
|
|
@@ -29129,13 +29342,17 @@ function coerceConfigureWarnings(warnings) {
|
|
|
29129
29342
|
}
|
|
29130
29343
|
async function sendIgnoredMessage2(client, sessionID, text) {
|
|
29131
29344
|
const typedClient = client;
|
|
29132
|
-
const
|
|
29133
|
-
|
|
29134
|
-
|
|
29135
|
-
|
|
29136
|
-
parts: [{ type: "text", text, ignored: true }]
|
|
29137
|
-
}
|
|
29345
|
+
const lastModel = await getLastUserModel(client, sessionID);
|
|
29346
|
+
const body = {
|
|
29347
|
+
noReply: true,
|
|
29348
|
+
parts: [{ type: "text", text, ignored: true }]
|
|
29138
29349
|
};
|
|
29350
|
+
if (lastModel) {
|
|
29351
|
+
body.model = { providerID: lastModel.providerID, modelID: lastModel.modelID };
|
|
29352
|
+
if (lastModel.variant)
|
|
29353
|
+
body.variant = lastModel.variant;
|
|
29354
|
+
}
|
|
29355
|
+
const promptInput = { path: { id: sessionID }, body };
|
|
29139
29356
|
if (typeof typedClient.session?.prompt === "function") {
|
|
29140
29357
|
await Promise.resolve(typedClient.session.prompt(promptInput));
|
|
29141
29358
|
return;
|
|
@@ -29171,6 +29388,8 @@ var plugin = async (input) => {
|
|
|
29171
29388
|
const configOverrides = {};
|
|
29172
29389
|
if (aftConfig.format_on_edit !== undefined)
|
|
29173
29390
|
configOverrides.format_on_edit = aftConfig.format_on_edit;
|
|
29391
|
+
if (aftConfig.formatter_timeout_secs !== undefined)
|
|
29392
|
+
configOverrides.formatter_timeout_secs = aftConfig.formatter_timeout_secs;
|
|
29174
29393
|
if (aftConfig.validate_on_edit !== undefined)
|
|
29175
29394
|
configOverrides.validate_on_edit = aftConfig.validate_on_edit;
|
|
29176
29395
|
if (aftConfig.formatter !== undefined)
|
|
@@ -29306,6 +29525,15 @@ ${lines}
|
|
|
29306
29525
|
pluginVersion: PLUGIN_VERSION,
|
|
29307
29526
|
projectRoot
|
|
29308
29527
|
}, validWarnings);
|
|
29528
|
+
},
|
|
29529
|
+
onBashCompletion: (completion, bridge) => {
|
|
29530
|
+
handlePushedBgCompletion({
|
|
29531
|
+
ctx,
|
|
29532
|
+
directory: input.directory,
|
|
29533
|
+
sessionID: completion.session_id,
|
|
29534
|
+
client: input.client,
|
|
29535
|
+
isActive: () => bridge.hasPendingRequests()
|
|
29536
|
+
}, completion);
|
|
29309
29537
|
}
|
|
29310
29538
|
}, configOverrides);
|
|
29311
29539
|
const ctx = {
|
package/dist/logger.d.ts
CHANGED
|
@@ -7,8 +7,8 @@ export declare function error(message: string, data?: unknown): void;
|
|
|
7
7
|
* a session's tool call). Bridge-lifecycle logs (spawn, version, idle) are
|
|
8
8
|
* project-scoped, not session-scoped — use `log`/`warn`/`error` for those.
|
|
9
9
|
*/
|
|
10
|
-
export declare function sessionLog(sessionId: string, message: string, data?: unknown): void;
|
|
11
|
-
export declare function sessionWarn(sessionId: string, message: string, data?: unknown): void;
|
|
12
|
-
export declare function sessionError(sessionId: string, message: string, data?: unknown): void;
|
|
10
|
+
export declare function sessionLog(sessionId: string | undefined, message: string, data?: unknown): void;
|
|
11
|
+
export declare function sessionWarn(sessionId: string | undefined, message: string, data?: unknown): void;
|
|
12
|
+
export declare function sessionError(sessionId: string | undefined, message: string, data?: unknown): void;
|
|
13
13
|
export declare function getLogFilePath(): string;
|
|
14
14
|
//# sourceMappingURL=logger.d.ts.map
|
package/dist/logger.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"logger.d.ts","sourceRoot":"","sources":["../src/logger.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"logger.d.ts","sourceRoot":"","sources":["../src/logger.ts"],"names":[],"mappings":"AAuEA,wBAAgB,GAAG,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,OAAO,GAAG,IAAI,CAEzD;AAED,wBAAgB,IAAI,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,OAAO,GAAG,IAAI,CAE1D;AAED,wBAAgB,KAAK,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,OAAO,GAAG,IAAI,CAE3D;AAED;;;;;GAKG;AACH,wBAAgB,UAAU,CAAC,SAAS,EAAE,MAAM,GAAG,SAAS,EAAE,OAAO,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,OAAO,GAAG,IAAI,CAE/F;AAED,wBAAgB,WAAW,CAAC,SAAS,EAAE,MAAM,GAAG,SAAS,EAAE,OAAO,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,OAAO,GAAG,IAAI,CAEhG;AAED,wBAAgB,YAAY,CAAC,SAAS,EAAE,MAAM,GAAG,SAAS,EAAE,OAAO,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,OAAO,GAAG,IAAI,CAEjG;AAED,wBAAgB,cAAc,IAAI,MAAM,CAEvC"}
|
package/dist/lsp.d.ts
CHANGED
|
@@ -27,6 +27,6 @@ export declare const LSP_SYMBOL_KIND_MAP: Record<number, string>;
|
|
|
27
27
|
* Failures are silent at the caller level — the binary falls back to
|
|
28
28
|
* tree-sitter-only disambiguation when `lsp_hints` is absent.
|
|
29
29
|
*/
|
|
30
|
-
export declare function queryLspHints(client: PluginInput["client"], symbolName: string, directory?: string): Promise<LspHints | undefined>;
|
|
30
|
+
export declare function queryLspHints(client: PluginInput["client"], symbolName: string, directory?: string, sessionId?: string): Promise<LspHints | undefined>;
|
|
31
31
|
export {};
|
|
32
32
|
//# sourceMappingURL=lsp.d.ts.map
|
package/dist/lsp.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"lsp.d.ts","sourceRoot":"","sources":["../src/lsp.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,qBAAqB,CAAC;AAGvD,mEAAmE;AACnE,UAAU,aAAa;IACrB,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,CAAC,EAAE,MAAM,CAAC;CACf;AAED,8DAA8D;AAC9D,MAAM,WAAW,QAAQ;IACvB,OAAO,EAAE,aAAa,EAAE,CAAC;CAC1B;AAED;;;;GAIG;AACH,eAAO,MAAM,mBAAmB,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAOtD,CAAC;AAEF;;;;;;;;;;GAUG;AACH,wBAAsB,aAAa,CACjC,MAAM,EAAE,WAAW,CAAC,QAAQ,CAAC,EAC7B,UAAU,EAAE,MAAM,EAClB,SAAS,CAAC,EAAE,MAAM,GACjB,OAAO,CAAC,QAAQ,GAAG,SAAS,CAAC,CAwD/B"}
|
|
1
|
+
{"version":3,"file":"lsp.d.ts","sourceRoot":"","sources":["../src/lsp.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,qBAAqB,CAAC;AAGvD,mEAAmE;AACnE,UAAU,aAAa;IACrB,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,CAAC,EAAE,MAAM,CAAC;CACf;AAED,8DAA8D;AAC9D,MAAM,WAAW,QAAQ;IACvB,OAAO,EAAE,aAAa,EAAE,CAAC;CAC1B;AAED;;;;GAIG;AACH,eAAO,MAAM,mBAAmB,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAOtD,CAAC;AAEF;;;;;;;;;;GAUG;AACH,wBAAsB,aAAa,CACjC,MAAM,EAAE,WAAW,CAAC,QAAQ,CAAC,EAC7B,UAAU,EAAE,MAAM,EAClB,SAAS,CAAC,EAAE,MAAM,EAClB,SAAS,CAAC,EAAE,MAAM,GACjB,OAAO,CAAC,QAAQ,GAAG,SAAS,CAAC,CAwD/B"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"notifications.d.ts","sourceRoot":"","sources":["../src/notifications.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;
|
|
1
|
+
{"version":3,"file":"notifications.d.ts","sourceRoot":"","sources":["../src/notifications.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AA0OH,MAAM,WAAW,mBAAmB;IAClC,8BAA8B;IAC9B,MAAM,EAAE,OAAO,CAAC;IAChB,iDAAiD;IACjD,SAAS,EAAE,MAAM,CAAC;IAClB,sEAAsE;IACtE,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED,MAAM,WAAW,gBAAgB;IAC/B,IAAI,EAAE,yBAAyB,GAAG,uBAAuB,GAAG,oBAAoB,CAAC;IACjF,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,IAAI,EAAE,MAAM,CAAC;CACd;AAED,MAAM,WAAW,uBAAuB;IACtC,MAAM,EAAE,OAAO,CAAC;IAChB,SAAS,EAAE,MAAM,CAAC;IAClB,UAAU,EAAE,MAAM,CAAC;IACnB,aAAa,EAAE,MAAM,CAAC;IACtB,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAED;;;;GAIG;AACH,wBAAsB,WAAW,CAAC,IAAI,EAAE,mBAAmB,EAAE,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAW3F;AAED;;;;GAIG;AACH,wBAAsB,UAAU,CAAC,IAAI,EAAE,mBAAmB,EAAE,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CA0C1F;AAED;;;;;GAKG;AACH,wBAAsB,uBAAuB,CAC3C,IAAI,EAAE,mBAAmB,EACzB,OAAO,EAAE,MAAM,EACf,QAAQ,EAAE,MAAM,EAAE,EAClB,UAAU,CAAC,EAAE,MAAM,GAClB,OAAO,CAAC,IAAI,CAAC,CAqCf;AAwED,wBAAsB,wBAAwB,CAC5C,IAAI,EAAE,uBAAuB,EAC7B,QAAQ,EAAE,gBAAgB,EAAE,GAC3B,OAAO,CAAC,IAAI,CAAC,CAwBf;AAED;;;GAGG;AACH,wBAAsB,eAAe,CAAC,IAAI,EAAE,mBAAmB,GAAG,OAAO,CAAC,IAAI,CAAC,CA0C9E"}
|
package/dist/pool.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"pool.d.ts","sourceRoot":"","sources":["../src/pool.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,KAAK,aAAa,EAAE,MAAM,UAAU,CAAC;AAY5D,MAAM,WAAW,WAAY,SAAQ,aAAa;IAChD,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,aAAa,CAAC,EAAE,MAAM,CAAC;CACxB;AAED;;;;;;;;;;;;;;;GAeG;AACH,qBAAa,UAAU;IACrB,iEAAiE;IACjE,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAgC;IACxD,OAAO,CAAC,UAAU,CAAS;IAC3B,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAS;IACrC,OAAO,CAAC,QAAQ,CAAC,aAAa,CAAS;IACvC,OAAO,CAAC,QAAQ,CAAC,aAAa,CAAgB;IAC9C,OAAO,CAAC,QAAQ,CAAC,eAAe,CAA0B;IAC1D,OAAO,CAAC,YAAY,CAA+C;gBAGjE,UAAU,EAAE,MAAM,EAClB,OAAO,GAAE,WAAgB,EACzB,eAAe,GAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAM;
|
|
1
|
+
{"version":3,"file":"pool.d.ts","sourceRoot":"","sources":["../src/pool.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,KAAK,aAAa,EAAE,MAAM,UAAU,CAAC;AAY5D,MAAM,WAAW,WAAY,SAAQ,aAAa;IAChD,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,aAAa,CAAC,EAAE,MAAM,CAAC;CACxB;AAED;;;;;;;;;;;;;;;GAeG;AACH,qBAAa,UAAU;IACrB,iEAAiE;IACjE,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAgC;IACxD,OAAO,CAAC,UAAU,CAAS;IAC3B,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAS;IACrC,OAAO,CAAC,QAAQ,CAAC,aAAa,CAAS;IACvC,OAAO,CAAC,QAAQ,CAAC,aAAa,CAAgB;IAC9C,OAAO,CAAC,QAAQ,CAAC,eAAe,CAA0B;IAC1D,OAAO,CAAC,YAAY,CAA+C;gBAGjE,UAAU,EAAE,MAAM,EAClB,OAAO,GAAE,WAAgB,EACzB,eAAe,GAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAM;IAqB/C;;;;;;;OAOG;IACH,kBAAkB,CAAC,WAAW,EAAE,MAAM,GAAG,YAAY,GAAG,IAAI;IAgB5D;;;;;;;OAOG;IACH,SAAS,CAAC,WAAW,EAAE,MAAM,GAAG,YAAY;IAmB5C,wEAAwE;IACxE,OAAO,CAAC,OAAO;IAUf,yDAAyD;IACzD,OAAO,CAAC,QAAQ;IAgBhB,wDAAwD;IAClD,QAAQ,IAAI,OAAO,CAAC,IAAI,CAAC;IAU/B;;;OAGG;IACG,aAAa,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAcnD,4CAA4C;IAC5C,IAAI,IAAI,IAAI,MAAM,CAEjB;CACF"}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Cache-busting fix: every PromptInput we send to OpenCode starts a new
|
|
3
|
+
* assistant turn (via `noReply: false`) or stores a new user message
|
|
4
|
+
* (via `noReply: true`). OpenCode's `createUserMessage` (in
|
|
5
|
+
* `packages/opencode/src/session/prompt.ts`) only preserves the user's
|
|
6
|
+
* model variant if we pass it explicitly — otherwise it falls back to
|
|
7
|
+
* `agent.variant` or undefined, silently switching the next assistant
|
|
8
|
+
* turn off the variant the human chose. That switch evicts the
|
|
9
|
+
* provider's prefix cache on every notification.
|
|
10
|
+
*
|
|
11
|
+
* This module fetches the last real user message's `{ providerID,
|
|
12
|
+
* modelID, variant }` and exposes it so prompt callers can include it
|
|
13
|
+
* verbatim in `PromptInput.model` + `PromptInput.variant`. Results are
|
|
14
|
+
* memoised per session for a short window so a batch of warnings or
|
|
15
|
+
* bg-completions only hits the messages API once.
|
|
16
|
+
*/
|
|
17
|
+
export interface LastUserModel {
|
|
18
|
+
providerID: string;
|
|
19
|
+
modelID: string;
|
|
20
|
+
variant?: string;
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Returns the most recent real user message's model + variant for the
|
|
24
|
+
* session, or `null` when the session has no user messages or the
|
|
25
|
+
* client doesn't expose `session.messages`. Result is cached for
|
|
26
|
+
* {@link CACHE_TTL_MS} ms per session — long enough to coalesce a
|
|
27
|
+
* batch of notifications, short enough that an actual variant change
|
|
28
|
+
* inside the conversation is picked up promptly.
|
|
29
|
+
*
|
|
30
|
+
* NOTE: Skips synthetic user messages we just produced ourselves
|
|
31
|
+
* (those whose only parts are `ignored: true`). Otherwise our own
|
|
32
|
+
* announcement could pin the variant after a single round-trip and
|
|
33
|
+
* defeat the fix.
|
|
34
|
+
*/
|
|
35
|
+
export declare function getLastUserModel(client: unknown, sessionId: string): Promise<LastUserModel | null>;
|
|
36
|
+
/** Test-only: drop the cache so unit tests can simulate fresh sessions. */
|
|
37
|
+
export declare function __resetLastUserModelCacheForTests(): void;
|
|
38
|
+
//# sourceMappingURL=last-user-model.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"last-user-model.d.ts","sourceRoot":"","sources":["../../src/shared/last-user-model.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAwBH,MAAM,WAAW,aAAa;IAC5B,UAAU,EAAE,MAAM,CAAC;IACnB,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAWD;;;;;;;;;;;;GAYG;AACH,wBAAsB,gBAAgB,CACpC,MAAM,EAAE,OAAO,EACf,SAAS,EAAE,MAAM,GAChB,OAAO,CAAC,aAAa,GAAG,IAAI,CAAC,CA0C/B;AAiBD,2EAA2E;AAC3E,wBAAgB,iCAAiC,IAAI,IAAI,CAExD"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"rpc-server.d.ts","sourceRoot":"","sources":["../../src/shared/rpc-server.ts"],"names":[],"mappings":"AAOA,KAAK,UAAU,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAAK,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC;AAExF,qBAAa,YAAY;IACvB,OAAO,CAAC,MAAM,CAAuB;IACrC,OAAO,CAAC,IAAI,CAAK;IACjB,OAAO,CAAC,KAAK,CAAuB;IACpC,OAAO,CAAC,QAAQ,CAAiC;IACjD,OAAO,CAAC,YAAY,CAAS;gBAEjB,UAAU,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM;IAIjD,sCAAsC;IACtC,MAAM,CAAC,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,UAAU,GAAG,IAAI;IAIjD,6DAA6D;IACvD,KAAK,IAAI,OAAO,CAAC,MAAM,CAAC;IAuC9B,8CAA8C;IAC9C,IAAI,IAAI,IAAI;IAaZ,OAAO,CAAC,QAAQ;
|
|
1
|
+
{"version":3,"file":"rpc-server.d.ts","sourceRoot":"","sources":["../../src/shared/rpc-server.ts"],"names":[],"mappings":"AAOA,KAAK,UAAU,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAAK,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC;AAExF,qBAAa,YAAY;IACvB,OAAO,CAAC,MAAM,CAAuB;IACrC,OAAO,CAAC,IAAI,CAAK;IACjB,OAAO,CAAC,KAAK,CAAuB;IACpC,OAAO,CAAC,QAAQ,CAAiC;IACjD,OAAO,CAAC,YAAY,CAAS;gBAEjB,UAAU,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM;IAIjD,sCAAsC;IACtC,MAAM,CAAC,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,UAAU,GAAG,IAAI;IAIjD,6DAA6D;IACvD,KAAK,IAAI,OAAO,CAAC,MAAM,CAAC;IAuC9B,8CAA8C;IAC9C,IAAI,IAAI,IAAI;IAaZ,OAAO,CAAC,QAAQ;CAoEjB"}
|
package/dist/tools/bash.d.ts
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import type { ToolDefinition } from "@opencode-ai/plugin";
|
|
2
2
|
import type { PluginContext } from "../types.js";
|
|
3
3
|
export declare function createBashTool(ctx: PluginContext): ToolDefinition;
|
|
4
|
+
export declare function createBashStatusTool(ctx: PluginContext): ToolDefinition;
|
|
5
|
+
export declare function createBashKillTool(ctx: PluginContext): ToolDefinition;
|
|
4
6
|
//# sourceMappingURL=bash.d.ts.map
|
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":"AAAA,OAAO,KAAK,EAAe,cAAc,EAAE,MAAM,qBAAqB,CAAC;AAKvE,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAsDjD,wBAAgB,cAAc,CAAC,GAAG,EAAE,aAAa,GAAG,cAAc,
|
|
1
|
+
{"version":3,"file":"bash.d.ts","sourceRoot":"","sources":["../../src/tools/bash.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAe,cAAc,EAAE,MAAM,qBAAqB,CAAC;AAKvE,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAsDjD,wBAAgB,cAAc,CAAC,GAAG,EAAE,aAAa,GAAG,cAAc,CAoJjE;AAED,wBAAgB,oBAAoB,CAAC,GAAG,EAAE,aAAa,GAAG,cAAc,CA4BvE;AAED,wBAAgB,kBAAkB,CAAC,GAAG,EAAE,aAAa,GAAG,cAAc,CAmBrE"}
|
|
@@ -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;AAI1D,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAkEjD,wEAAwE;AACxE,eAAO,MAAM,wBAAwB,GAAI,IAAI,MAAM,EAAE,QAAQ,MAAM,EAAE,OAAO,MAAM,KAAG,MAChD,CAAC;AA+QtC;;GAEG;AACH,wBAAgB,cAAc,CAAC,GAAG,EAAE,aAAa,GAAG,cAAc,CAgJjE;
|
|
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;AAI1D,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAkEjD,wEAAwE;AACxE,eAAO,MAAM,wBAAwB,GAAI,IAAI,MAAM,EAAE,QAAQ,MAAM,EAAE,OAAO,MAAM,KAAG,MAChD,CAAC;AA+QtC;;GAEG;AACH,wBAAgB,cAAc,CAAC,GAAG,EAAE,aAAa,GAAG,cAAc,CAgJjE;AAm+BD;;;GAGG;AACH,wBAAgB,YAAY,CAAC,GAAG,EAAE,aAAa,GAAG,MAAM,CAAC,MAAM,EAAE,cAAc,CAAC,CAY/E;AAMD;;GAEG;AACH,wBAAgB,gBAAgB,CAAC,GAAG,EAAE,aAAa,GAAG,MAAM,CAAC,MAAM,EAAE,cAAc,CAAC,CAmEnF"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"refactoring.d.ts","sourceRoot":"","sources":["../../src/tools/refactoring.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC;AAG1D,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAajD;;GAEG;AACH,wBAAgB,gBAAgB,CAAC,GAAG,EAAE,aAAa,GAAG,MAAM,CAAC,MAAM,EAAE,cAAc,CAAC,
|
|
1
|
+
{"version":3,"file":"refactoring.d.ts","sourceRoot":"","sources":["../../src/tools/refactoring.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC;AAG1D,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAajD;;GAEG;AACH,wBAAgB,gBAAgB,CAAC,GAAG,EAAE,aAAa,GAAG,MAAM,CAAC,MAAM,EAAE,cAAc,CAAC,CAgInF"}
|
package/dist/tui.js
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
// package.json
|
|
3
3
|
var package_default = {
|
|
4
4
|
name: "@cortexkit/aft-opencode",
|
|
5
|
-
version: "0.18.
|
|
5
|
+
version: "0.18.3",
|
|
6
6
|
type: "module",
|
|
7
7
|
description: "OpenCode plugin for Agent File Tools (AFT) \u2014 tree-sitter and lsp powered code analysis",
|
|
8
8
|
main: "dist/index.js",
|
|
@@ -16,6 +16,7 @@ var package_default = {
|
|
|
16
16
|
"dist",
|
|
17
17
|
"src/tui",
|
|
18
18
|
"src/shared",
|
|
19
|
+
"src/logger.ts",
|
|
19
20
|
"README.md"
|
|
20
21
|
],
|
|
21
22
|
scripts: {
|
|
@@ -24,6 +25,7 @@ var package_default = {
|
|
|
24
25
|
pretest: "cd ../.. && cargo build",
|
|
25
26
|
test: "bun test",
|
|
26
27
|
"test:e2e": "bun test src/__tests__/e2e/",
|
|
28
|
+
lint: "biome check src",
|
|
27
29
|
prepublishOnly: "bun run build"
|
|
28
30
|
},
|
|
29
31
|
dependencies: {
|
|
@@ -34,11 +36,11 @@ var package_default = {
|
|
|
34
36
|
zod: "^4.1.8"
|
|
35
37
|
},
|
|
36
38
|
optionalDependencies: {
|
|
37
|
-
"@cortexkit/aft-darwin-arm64": "0.18.
|
|
38
|
-
"@cortexkit/aft-darwin-x64": "0.18.
|
|
39
|
-
"@cortexkit/aft-linux-arm64": "0.18.
|
|
40
|
-
"@cortexkit/aft-linux-x64": "0.18.
|
|
41
|
-
"@cortexkit/aft-win32-x64": "0.18.
|
|
39
|
+
"@cortexkit/aft-darwin-arm64": "0.18.3",
|
|
40
|
+
"@cortexkit/aft-darwin-x64": "0.18.3",
|
|
41
|
+
"@cortexkit/aft-linux-arm64": "0.18.3",
|
|
42
|
+
"@cortexkit/aft-linux-x64": "0.18.3",
|
|
43
|
+
"@cortexkit/aft-win32-x64": "0.18.3"
|
|
42
44
|
},
|
|
43
45
|
devDependencies: {
|
|
44
46
|
"@types/node": "^22.0.0",
|
|
@@ -71,7 +73,8 @@ import * as fs from "fs";
|
|
|
71
73
|
import * as os from "os";
|
|
72
74
|
import * as path from "path";
|
|
73
75
|
var TAG = "[aft-plugin]";
|
|
74
|
-
var
|
|
76
|
+
var isTestEnv = process.env.BUN_TEST === "1" || false;
|
|
77
|
+
var logFile = path.join(os.tmpdir(), isTestEnv ? "aft-plugin-test.log" : "aft-plugin.log");
|
|
75
78
|
var useStderr = process.env.AFT_LOG_STDERR === "1";
|
|
76
79
|
var buffer = [];
|
|
77
80
|
var flushTimer = null;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cortexkit/aft-opencode",
|
|
3
|
-
"version": "0.18.
|
|
3
|
+
"version": "0.18.3",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "OpenCode plugin for Agent File Tools (AFT) — tree-sitter and lsp powered code analysis",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -14,6 +14,7 @@
|
|
|
14
14
|
"dist",
|
|
15
15
|
"src/tui",
|
|
16
16
|
"src/shared",
|
|
17
|
+
"src/logger.ts",
|
|
17
18
|
"README.md"
|
|
18
19
|
],
|
|
19
20
|
"scripts": {
|
|
@@ -22,6 +23,7 @@
|
|
|
22
23
|
"pretest": "cd ../.. && cargo build",
|
|
23
24
|
"test": "bun test",
|
|
24
25
|
"test:e2e": "bun test src/__tests__/e2e/",
|
|
26
|
+
"lint": "biome check src",
|
|
25
27
|
"prepublishOnly": "bun run build"
|
|
26
28
|
},
|
|
27
29
|
"dependencies": {
|
|
@@ -32,11 +34,11 @@
|
|
|
32
34
|
"zod": "^4.1.8"
|
|
33
35
|
},
|
|
34
36
|
"optionalDependencies": {
|
|
35
|
-
"@cortexkit/aft-darwin-arm64": "0.18.
|
|
36
|
-
"@cortexkit/aft-darwin-x64": "0.18.
|
|
37
|
-
"@cortexkit/aft-linux-arm64": "0.18.
|
|
38
|
-
"@cortexkit/aft-linux-x64": "0.18.
|
|
39
|
-
"@cortexkit/aft-win32-x64": "0.18.
|
|
37
|
+
"@cortexkit/aft-darwin-arm64": "0.18.3",
|
|
38
|
+
"@cortexkit/aft-darwin-x64": "0.18.3",
|
|
39
|
+
"@cortexkit/aft-linux-arm64": "0.18.3",
|
|
40
|
+
"@cortexkit/aft-linux-x64": "0.18.3",
|
|
41
|
+
"@cortexkit/aft-win32-x64": "0.18.3"
|
|
40
42
|
},
|
|
41
43
|
"devDependencies": {
|
|
42
44
|
"@types/node": "^22.0.0",
|
package/src/logger.ts
ADDED
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
import * as fs from "node:fs";
|
|
2
|
+
import * as os from "node:os";
|
|
3
|
+
import * as path from "node:path";
|
|
4
|
+
|
|
5
|
+
const TAG = "[aft-plugin]";
|
|
6
|
+
|
|
7
|
+
// Route test runs to a separate log file so `bun test` never pollutes the
|
|
8
|
+
// live session log that users read to diagnose problems. Bun sets BUN_TEST=1
|
|
9
|
+
// automatically; NODE_ENV=test covers other test harnesses.
|
|
10
|
+
const isTestEnv = process.env.BUN_TEST === "1" || process.env.NODE_ENV === "test";
|
|
11
|
+
const logFile = path.join(os.tmpdir(), isTestEnv ? "aft-plugin-test.log" : "aft-plugin.log");
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* When AFT_LOG_STDERR=1, logs go to stderr (useful for subprocess tests that
|
|
15
|
+
* capture stderr output). Otherwise logs go to the temp file.
|
|
16
|
+
*/
|
|
17
|
+
const useStderr = process.env.AFT_LOG_STDERR === "1";
|
|
18
|
+
|
|
19
|
+
let buffer: string[] = [];
|
|
20
|
+
let flushTimer: ReturnType<typeof setTimeout> | null = null;
|
|
21
|
+
const FLUSH_INTERVAL_MS = 500;
|
|
22
|
+
const BUFFER_SIZE_LIMIT = 50;
|
|
23
|
+
|
|
24
|
+
function flush(): void {
|
|
25
|
+
if (buffer.length === 0) return;
|
|
26
|
+
const data = buffer.join("");
|
|
27
|
+
buffer = [];
|
|
28
|
+
try {
|
|
29
|
+
if (useStderr) {
|
|
30
|
+
process.stderr.write(data);
|
|
31
|
+
} else {
|
|
32
|
+
fs.appendFileSync(logFile, data);
|
|
33
|
+
}
|
|
34
|
+
} catch {
|
|
35
|
+
// Intentional: logging must never throw
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function scheduleFlush(): void {
|
|
40
|
+
if (flushTimer) return;
|
|
41
|
+
flushTimer = setTimeout(() => {
|
|
42
|
+
flushTimer = null;
|
|
43
|
+
flush();
|
|
44
|
+
}, FLUSH_INTERVAL_MS);
|
|
45
|
+
// Don't prevent Node from exiting
|
|
46
|
+
if (flushTimer && typeof flushTimer === "object" && "unref" in flushTimer) {
|
|
47
|
+
flushTimer.unref();
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function write(level: string, message: string, data?: unknown, sessionId?: string): void {
|
|
52
|
+
try {
|
|
53
|
+
const timestamp = new Date().toISOString();
|
|
54
|
+
const serialized = data === undefined ? "" : ` ${JSON.stringify(data)}`;
|
|
55
|
+
const sessionPrefix = sessionId ? ` [${sessionId}]` : "";
|
|
56
|
+
const line = `[${timestamp}] ${level} ${TAG}${sessionPrefix} ${message}${serialized}\n`;
|
|
57
|
+
if (useStderr) {
|
|
58
|
+
// Write immediately in stderr mode (subprocess tests need it before exit)
|
|
59
|
+
process.stderr.write(line);
|
|
60
|
+
return;
|
|
61
|
+
}
|
|
62
|
+
buffer.push(line);
|
|
63
|
+
if (buffer.length >= BUFFER_SIZE_LIMIT) {
|
|
64
|
+
flush();
|
|
65
|
+
} else {
|
|
66
|
+
scheduleFlush();
|
|
67
|
+
}
|
|
68
|
+
} catch {
|
|
69
|
+
// Intentional: logging must never throw
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
export function log(message: string, data?: unknown): void {
|
|
73
|
+
write("INFO", message, data);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
export function warn(message: string, data?: unknown): void {
|
|
77
|
+
write("WARN", message, data);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
export function error(message: string, data?: unknown): void {
|
|
81
|
+
write("ERROR", message, data);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Log with a session-id prefix. Use for messages that originate from a
|
|
86
|
+
* specific OpenCode session (per-request errors, timeouts, crashes during
|
|
87
|
+
* a session's tool call). Bridge-lifecycle logs (spawn, version, idle) are
|
|
88
|
+
* project-scoped, not session-scoped — use `log`/`warn`/`error` for those.
|
|
89
|
+
*/
|
|
90
|
+
export function sessionLog(sessionId: string | undefined, message: string, data?: unknown): void {
|
|
91
|
+
write("INFO", message, data, sessionId);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
export function sessionWarn(sessionId: string | undefined, message: string, data?: unknown): void {
|
|
95
|
+
write("WARN", message, data, sessionId);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
export function sessionError(sessionId: string | undefined, message: string, data?: unknown): void {
|
|
99
|
+
write("ERROR", message, data, sessionId);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
export function getLogFilePath(): string {
|
|
103
|
+
return logFile;
|
|
104
|
+
}
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Cache-busting fix: every PromptInput we send to OpenCode starts a new
|
|
3
|
+
* assistant turn (via `noReply: false`) or stores a new user message
|
|
4
|
+
* (via `noReply: true`). OpenCode's `createUserMessage` (in
|
|
5
|
+
* `packages/opencode/src/session/prompt.ts`) only preserves the user's
|
|
6
|
+
* model variant if we pass it explicitly — otherwise it falls back to
|
|
7
|
+
* `agent.variant` or undefined, silently switching the next assistant
|
|
8
|
+
* turn off the variant the human chose. That switch evicts the
|
|
9
|
+
* provider's prefix cache on every notification.
|
|
10
|
+
*
|
|
11
|
+
* This module fetches the last real user message's `{ providerID,
|
|
12
|
+
* modelID, variant }` and exposes it so prompt callers can include it
|
|
13
|
+
* verbatim in `PromptInput.model` + `PromptInput.variant`. Results are
|
|
14
|
+
* memoised per session for a short window so a batch of warnings or
|
|
15
|
+
* bg-completions only hits the messages API once.
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
interface SessionMessageInfo {
|
|
19
|
+
id?: string;
|
|
20
|
+
role?: string;
|
|
21
|
+
parts?: Array<{ ignored?: boolean }>;
|
|
22
|
+
model?: {
|
|
23
|
+
providerID?: string;
|
|
24
|
+
modelID?: string;
|
|
25
|
+
variant?: string;
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
interface SessionMessage {
|
|
30
|
+
info?: SessionMessageInfo;
|
|
31
|
+
parts?: Array<{ ignored?: boolean }>;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
interface OpenCodeClientShape {
|
|
35
|
+
session?: {
|
|
36
|
+
messages?: (input: { path: { id: string } }) => Promise<{ data?: SessionMessage[] }>;
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export interface LastUserModel {
|
|
41
|
+
providerID: string;
|
|
42
|
+
modelID: string;
|
|
43
|
+
variant?: string;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
interface CacheEntry {
|
|
47
|
+
expiresAt: number;
|
|
48
|
+
model: LastUserModel | null;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
const CACHE_TTL_MS = 5_000;
|
|
52
|
+
const CACHE_MAX_ENTRIES = 100;
|
|
53
|
+
const cache = new Map<string, CacheEntry>();
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Returns the most recent real user message's model + variant for the
|
|
57
|
+
* session, or `null` when the session has no user messages or the
|
|
58
|
+
* client doesn't expose `session.messages`. Result is cached for
|
|
59
|
+
* {@link CACHE_TTL_MS} ms per session — long enough to coalesce a
|
|
60
|
+
* batch of notifications, short enough that an actual variant change
|
|
61
|
+
* inside the conversation is picked up promptly.
|
|
62
|
+
*
|
|
63
|
+
* NOTE: Skips synthetic user messages we just produced ourselves
|
|
64
|
+
* (those whose only parts are `ignored: true`). Otherwise our own
|
|
65
|
+
* announcement could pin the variant after a single round-trip and
|
|
66
|
+
* defeat the fix.
|
|
67
|
+
*/
|
|
68
|
+
export async function getLastUserModel(
|
|
69
|
+
client: unknown,
|
|
70
|
+
sessionId: string,
|
|
71
|
+
): Promise<LastUserModel | null> {
|
|
72
|
+
const now = Date.now();
|
|
73
|
+
const cached = cache.get(sessionId);
|
|
74
|
+
if (cached && cached.expiresAt > now) {
|
|
75
|
+
cache.delete(sessionId);
|
|
76
|
+
cache.set(sessionId, cached);
|
|
77
|
+
return cached.model;
|
|
78
|
+
}
|
|
79
|
+
if (cached) cache.delete(sessionId);
|
|
80
|
+
|
|
81
|
+
const c = client as OpenCodeClientShape;
|
|
82
|
+
const fetcher = c?.session?.messages;
|
|
83
|
+
if (typeof fetcher !== "function") {
|
|
84
|
+
return null;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
let messages: SessionMessage[];
|
|
88
|
+
try {
|
|
89
|
+
const result = await fetcher({ path: { id: sessionId } });
|
|
90
|
+
messages = result?.data ?? [];
|
|
91
|
+
} catch {
|
|
92
|
+
// Don't cache failures — a transient API error shouldn't pin the
|
|
93
|
+
// session's variant resolution to "unknown" for 5 s.
|
|
94
|
+
return null;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
for (let i = messages.length - 1; i >= 0; i--) {
|
|
98
|
+
const info = messages[i]?.info;
|
|
99
|
+
if (info?.role !== "user") continue;
|
|
100
|
+
if (isSyntheticIgnoredMessage(messages[i])) continue;
|
|
101
|
+
const model = info.model;
|
|
102
|
+
if (!model?.providerID || !model?.modelID) continue;
|
|
103
|
+
const resolved: LastUserModel = {
|
|
104
|
+
providerID: model.providerID,
|
|
105
|
+
modelID: model.modelID,
|
|
106
|
+
...(model.variant ? { variant: model.variant } : {}),
|
|
107
|
+
};
|
|
108
|
+
setCache(sessionId, resolved);
|
|
109
|
+
return resolved;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
return null;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
function setCache(sessionId: string, model: LastUserModel | null): void {
|
|
116
|
+
if (cache.has(sessionId)) cache.delete(sessionId);
|
|
117
|
+
cache.set(sessionId, { expiresAt: Date.now() + CACHE_TTL_MS, model });
|
|
118
|
+
while (cache.size > CACHE_MAX_ENTRIES) {
|
|
119
|
+
const oldest = cache.keys().next().value;
|
|
120
|
+
if (typeof oldest !== "string") break;
|
|
121
|
+
cache.delete(oldest);
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
function isSyntheticIgnoredMessage(message: SessionMessage | undefined): boolean {
|
|
126
|
+
const parts = message?.info?.parts ?? message?.parts;
|
|
127
|
+
return Array.isArray(parts) && parts.length > 0 && parts.every((part) => part?.ignored === true);
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/** Test-only: drop the cache so unit tests can simulate fresh sessions. */
|
|
131
|
+
export function __resetLastUserModelCacheForTests(): void {
|
|
132
|
+
cache.clear();
|
|
133
|
+
}
|
package/src/shared/rpc-server.ts
CHANGED
|
@@ -130,10 +130,11 @@ export class AftRpcServer {
|
|
|
130
130
|
|
|
131
131
|
const { token: _token, ...handlerParams } = params;
|
|
132
132
|
|
|
133
|
-
|
|
133
|
+
// Successful RPC calls are intentionally NOT logged. The /aft-status
|
|
134
|
+
// TUI dialog polls every 1.5s while open, which used to spam the log
|
|
135
|
+
// with RPC call/result pairs. Errors are still logged (real signal).
|
|
134
136
|
handler(handlerParams)
|
|
135
137
|
.then((result) => {
|
|
136
|
-
log(`RPC result: ${method} => ${JSON.stringify(result).slice(0, 200)}`);
|
|
137
138
|
res.writeHead(200, { "Content-Type": "application/json" });
|
|
138
139
|
res.end(JSON.stringify(result));
|
|
139
140
|
})
|