@cortexkit/aft-opencode 0.35.4 → 0.36.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,2 +1,2 @@
1
- export { maybeAppendConflictsHint, maybeAppendGrepHint } from "@cortexkit/aft-bridge";
1
+ export { maybeAppendConflictsHint } from "@cortexkit/aft-bridge";
2
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":"AAGA,OAAO,EAAE,wBAAwB,EAAE,mBAAmB,EAAE,MAAM,uBAAuB,CAAC"}
1
+ {"version":3,"file":"bash-hints.d.ts","sourceRoot":"","sources":["../../src/shared/bash-hints.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,wBAAwB,EAAE,MAAM,uBAAuB,CAAC"}
@@ -26,6 +26,8 @@ export declare class AftRpcClient {
26
26
  */
27
27
  private resolvePortInfos;
28
28
  private readAllPortFiles;
29
+ /** Delete a port file whose owning process is provably dead (best-effort). */
30
+ private reclaimDeadPortFile;
29
31
  private parsePortFile;
30
32
  private portFailureKey;
31
33
  private clearPortFailure;
@@ -1 +1 @@
1
- {"version":3,"file":"rpc-client.d.ts","sourceRoot":"","sources":["../../src/shared/rpc-client.ts"],"names":[],"mappings":"AAaA,MAAM,WAAW,iBAAiB;IAChC,MAAM,CAAC,EAAE,WAAW,CAAC;CACtB;AA4BD,qBAAa,YAAY;IACvB,OAAO,CAAC,IAAI,CAAuB;IACnC,OAAO,CAAC,KAAK,CAAuB;IACpC,OAAO,CAAC,QAAQ,CAAS;IACzB,OAAO,CAAC,cAAc,CAAS;IAC/B,OAAO,CAAC,iBAAiB,CAA6B;gBAE1C,UAAU,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM;IAKjD,iFAAiF;IAC3E,IAAI,CAAC,CAAC,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EACpC,MAAM,EAAE,MAAM,EACd,MAAM,GAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAM,EACpC,OAAO,GAAE,iBAAsB,GAC9B,OAAO,CAAC,CAAC,CAAC;YAyCC,OAAO;IAsBrB;;;;OAIG;IACH,OAAO,CAAC,oBAAoB;IAM5B,4CAA4C;IACtC,WAAW,IAAI,OAAO,CAAC,OAAO,CAAC;IASrC;;;;OAIG;YACW,gBAAgB;IAwB9B,OAAO,CAAC,gBAAgB;IAiCxB,OAAO,CAAC,aAAa;IAuBrB,OAAO,CAAC,cAAc;IAKtB,OAAO,CAAC,gBAAgB;IAKxB,OAAO,CAAC,iBAAiB;YAsBX,WAAW;YAcX,gBAAgB;IAmB9B,KAAK,IAAI,IAAI;CAKd"}
1
+ {"version":3,"file":"rpc-client.d.ts","sourceRoot":"","sources":["../../src/shared/rpc-client.ts"],"names":[],"mappings":"AAYA,MAAM,WAAW,iBAAiB;IAChC,MAAM,CAAC,EAAE,WAAW,CAAC;CACtB;AA4BD,qBAAa,YAAY;IACvB,OAAO,CAAC,IAAI,CAAuB;IACnC,OAAO,CAAC,KAAK,CAAuB;IACpC,OAAO,CAAC,QAAQ,CAAS;IACzB,OAAO,CAAC,cAAc,CAAS;IAC/B,OAAO,CAAC,iBAAiB,CAA6B;gBAE1C,UAAU,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM;IAKjD,iFAAiF;IAC3E,IAAI,CAAC,CAAC,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EACpC,MAAM,EAAE,MAAM,EACd,MAAM,GAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAM,EACpC,OAAO,GAAE,iBAAsB,GAC9B,OAAO,CAAC,CAAC,CAAC;YAyCC,OAAO;IAsBrB;;;;OAIG;IACH,OAAO,CAAC,oBAAoB;IAM5B,4CAA4C;IACtC,WAAW,IAAI,OAAO,CAAC,OAAO,CAAC;IASrC;;;;OAIG;YACW,gBAAgB;IAwB9B,OAAO,CAAC,gBAAgB;IAwDxB,8EAA8E;IAC9E,OAAO,CAAC,mBAAmB;IAQ3B,OAAO,CAAC,aAAa;IASrB,OAAO,CAAC,cAAc;IAKtB,OAAO,CAAC,gBAAgB;IAKxB,OAAO,CAAC,iBAAiB;YAsBX,WAAW;YAkCX,gBAAgB;IAmB9B,KAAK,IAAI,IAAI;CAKd"}
@@ -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;IAC7B,OAAO,CAAC,QAAQ,CAAS;IACzB,oFAAoF;IACpF,OAAO,CAAC,UAAU,CAAS;gBAEf,UAAU,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM;IAMjD,sCAAsC;IACtC,MAAM,CAAC,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,UAAU,GAAG,IAAI;IAIjD,6DAA6D;IACvD,KAAK,IAAI,OAAO,CAAC,MAAM,CAAC;IA0C9B,8CAA8C;IAC9C,IAAI,IAAI,IAAI;IAaZ,OAAO,CAAC,QAAQ;CAoEjB"}
1
+ {"version":3,"file":"rpc-server.d.ts","sourceRoot":"","sources":["../../src/shared/rpc-server.ts"],"names":[],"mappings":"AAOA,KAAK,UAAU,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAAK,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC;AAExF,qBAAa,YAAY;IACvB,OAAO,CAAC,MAAM,CAAuB;IACrC,OAAO,CAAC,IAAI,CAAK;IACjB,OAAO,CAAC,KAAK,CAAuB;IACpC,OAAO,CAAC,QAAQ,CAAiC;IACjD,OAAO,CAAC,YAAY,CAAS;IAC7B,OAAO,CAAC,QAAQ,CAAS;IACzB,oFAAoF;IACpF,OAAO,CAAC,UAAU,CAAS;gBAEf,UAAU,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM;IAMjD,sCAAsC;IACtC,MAAM,CAAC,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,UAAU,GAAG,IAAI;IAIjD,6DAA6D;IACvD,KAAK,IAAI,OAAO,CAAC,MAAM,CAAC;IAmD9B,8CAA8C;IAC9C,IAAI,IAAI,IAAI;IAaZ,OAAO,CAAC,QAAQ;CAoEjB"}
@@ -26,4 +26,27 @@ export declare function rpcPortFilePath(storageDir: string, directory: string):
26
26
  * last-write-wins racing under `--port 0`).
27
27
  */
28
28
  export declare function rpcPortFileDir(storageDir: string, directory: string): string;
29
+ /**
30
+ * Contents of a per-instance RPC port file. `pid` + `started_at` let the client
31
+ * skip files whose owning process is dead (no health-check round-trip) and pick
32
+ * the freshest live server, instead of wading through every crash/restart
33
+ * leftover. Older files carry only `{ port, token }` (no pid); those are treated
34
+ * as "can't prove dead" and fall back to the health-check path.
35
+ */
36
+ export interface RpcPortRecord {
37
+ port: number;
38
+ token: string | null;
39
+ /** PID of the plugin process that owns this server, if recorded. */
40
+ pid?: number;
41
+ /** `Date.now()` when this server started, for newest-first ordering. */
42
+ started_at?: number;
43
+ }
44
+ /** True if `pid` names a live process. `process.kill(pid, 0)` sends no signal. */
45
+ export declare function isPidAlive(pid: number | undefined): boolean;
46
+ /**
47
+ * Parse a port file's contents into a record. Accepts the current JSON shape
48
+ * (`{ port, token, pid?, started_at? }`) and the legacy bare-integer format
49
+ * (unauthenticated, pre-v0.28.2). Returns `null` for unusable contents.
50
+ */
51
+ export declare function parseRpcPortRecord(content: string): RpcPortRecord | null;
29
52
  //# sourceMappingURL=rpc-utils.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"rpc-utils.d.ts","sourceRoot":"","sources":["../../src/shared/rpc-utils.ts"],"names":[],"mappings":"AAGA;;;;GAIG;AACH,wBAAgB,WAAW,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM,CAIrD;AAED;;;;;;;;;GASG;AACH,wBAAgB,eAAe,CAAC,UAAU,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,MAAM,CAG7E;AAED;;;;;;;;;GASG;AACH,wBAAgB,cAAc,CAAC,UAAU,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,MAAM,CAG5E"}
1
+ {"version":3,"file":"rpc-utils.d.ts","sourceRoot":"","sources":["../../src/shared/rpc-utils.ts"],"names":[],"mappings":"AAGA;;;;GAIG;AACH,wBAAgB,WAAW,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM,CAIrD;AAED;;;;;;;;;GASG;AACH,wBAAgB,eAAe,CAAC,UAAU,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,MAAM,CAG7E;AAED;;;;;;;;;GASG;AACH,wBAAgB,cAAc,CAAC,UAAU,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,MAAM,CAG5E;AAED;;;;;;GAMG;AACH,MAAM,WAAW,aAAa;IAC5B,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IACrB,oEAAoE;IACpE,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,wEAAwE;IACxE,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAED,kFAAkF;AAClF,wBAAgB,UAAU,CAAC,GAAG,EAAE,MAAM,GAAG,SAAS,GAAG,OAAO,CAS3D;AAED;;;;GAIG;AACH,wBAAgB,kBAAkB,CAAC,OAAO,EAAE,MAAM,GAAG,aAAa,GAAG,IAAI,CA2BxE"}
@@ -5,7 +5,7 @@ import { type StatusBarCounts } from "@cortexkit/aft-bridge";
5
5
  * (unchanged since last emit and heartbeat not elapsed) or when counts are
6
6
  * absent (no inspect scan has populated Tier-2 yet).
7
7
  */
8
- export declare function statusBarSuffixForSession(sessionID: string, counts: StatusBarCounts | undefined): string;
8
+ export declare function statusBarSuffixForSession(sessionID: string, counts: StatusBarCounts | undefined, force?: boolean): string;
9
9
  /** Drop a session's emit state (on session delete/shutdown). */
10
10
  export declare function clearStatusBarSession(sessionID: string): void;
11
11
  //# sourceMappingURL=status-bar-inject.d.ts.map
@@ -1 +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"}
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,EACnC,KAAK,UAAQ,GACZ,MAAM,CAkBR;AAED,gEAAgE;AAChE,wBAAgB,qBAAqB,CAAC,SAAS,EAAE,MAAM,GAAG,IAAI,CAE7D"}
@@ -0,0 +1,17 @@
1
+ import type { ToolDefinition } from "@opencode-ai/plugin";
2
+ /**
3
+ * Mark the instant just before a bridge request is sent. First mark wins so a
4
+ * tool that issues several bridge requests still reports one bridge window
5
+ * (first send -> last response); the intervening plugin work folds into it.
6
+ */
7
+ export declare function markBridgeStart(): void;
8
+ /** Mark the instant just after the bridge responds (last response wins). */
9
+ export declare function markBridgeEnd(): void;
10
+ /**
11
+ * Wrap every tool's execute() so each invocation logs a one-line latency
12
+ * breakdown: total, pre-bridge (arg/permission/session-dir work), bridge
13
+ * round-trip, and post-bridge (result formatting). Mutates execute in place;
14
+ * call after the final tool surface is built so disabled tools aren't wrapped.
15
+ */
16
+ export declare function instrumentToolMap(tools: Record<string, ToolDefinition>): Record<string, ToolDefinition>;
17
+ //# sourceMappingURL=tool-perf.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tool-perf.d.ts","sourceRoot":"","sources":["../src/tool-perf.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC;AAmB1D;;;;GAIG;AACH,wBAAgB,eAAe,IAAI,IAAI,CAKtC;AAED,4EAA4E;AAC5E,wBAAgB,aAAa,IAAI,IAAI,CAKpC;AAmBD;;;;;GAKG;AACH,wBAAgB,iBAAiB,CAC/B,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,cAAc,CAAC,GACpC,MAAM,CAAC,MAAM,EAAE,cAAc,CAAC,CAoBhC"}
@@ -1 +1 @@
1
- {"version":3,"file":"_shared.d.ts","sourceRoot":"","sources":["../../src/tools/_shared.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAKH,OAAO,KAAK,EAAE,YAAY,EAAE,oBAAoB,EAAE,MAAM,uBAAuB,CAAC;AAQhF,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAIjD;;;;;;;;;;;;;;;;;;;;;GAqBG;AAEH,eAAO,MAAM,WAAW,GAAI,KAAK,MAAM,EAAE,KAAK,MAAM,KAAG,GACR,CAAC;AAEhD;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,iBAAiB,CAC/B,CAAC,EAAE,OAAO,EACV,SAAS,EAAE,MAAM,EACjB,GAAG,EAAE,MAAM,EACX,GAAG,EAAE,MAAM,GACV,MAAM,GAAG,SAAS,CAWpB;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,YAAY,CAAC,KAAK,EAAE,OAAO,GAAG,OAAO,CAMpD;AAED;;;;;;;GAOG;AAKH,eAAO,MAAM,yBAAyB,QAAS,CAAC;AAEhD,eAAO,MAAM,+BAA+B,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CASlE,CAAC;AAEF,wFAAwF;AACxF,wBAAgB,iBAAiB,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAErE;AA2BD,8EAA8E;AAC9E,wBAAgB,wBAAwB,CACtC,OAAO,EAAE,MAAM,EACf,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EACjC,MAAM,GAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAM,GACnC,MAAM,CAsDR;AAsBD;;;;;;GAMG;AACH,MAAM,WAAW,WAAW;IAC1B,wEAAwE;IACxE,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,oDAAoD;IACpD,SAAS,EAAE,MAAM,CAAC;IAClB,6EAA6E;IAC7E,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAkBD;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,cAAc,CAAC,OAAO,EAAE,WAAW,GAAG,MAAM,CAW3D;AAED;;;;;GAKG;AACH,wBAAsB,kBAAkB,CACtC,GAAG,EAAE,aAAa,EAClB,OAAO,EAAE,WAAW,GACnB,OAAO,CAAC,MAAM,CAAC,CAKjB;AAED;;;;;;GAMG;AACH,wBAAgB,WAAW,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAQjD;AAED;;2BAE2B;AAC3B,wBAAgB,0BAA0B,CAAC,WAAW,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,MAAM,CAGtF;AAED,wBAAsB,cAAc,CAClC,GAAG,EAAE,aAAa,EAClB,OAAO,EAAE,WAAW,EACpB,MAAM,EAAE,MAAM,GACb,OAAO,CAAC,MAAM,CAAC,CAEjB;AAED;;;;;;;;;GASG;AACH,wBAAgB,SAAS,CAAC,GAAG,EAAE,aAAa,EAAE,OAAO,EAAE,WAAW,GAAG,YAAY,CAEhF;AAED;;;;;;;;;;;;;;GAcG;AACH,wBAAsB,UAAU,CAC9B,GAAG,EAAE,aAAa,EAClB,OAAO,EAAE,WAAW,EACpB,OAAO,EAAE,MAAM,EACf,MAAM,GAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAM,EACpC,OAAO,CAAC,EAAE,oBAAoB,GAC7B,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CA0BlC;AAED;;;;;GAKG;AACH,wBAAsB,cAAc,CAClC,GAAG,EAAE,aAAa,EAClB,OAAO,EAAE,WAAW,EACpB,OAAO,EAAE,MAAM,EACf,MAAM,GAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAM,EACpC,OAAO,CAAC,EAAE,oBAAoB,GAC7B,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAMlC;AAED;;;;;GAKG;AACH,wBAAgB,+BAA+B,CAAC,GAAG,EAAE,aAAa,EAAE,OAAO,EAAE,WAAW,GAAG,IAAI,CAE9F"}
1
+ {"version":3,"file":"_shared.d.ts","sourceRoot":"","sources":["../../src/tools/_shared.ts"],"names":[],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"bash.d.ts","sourceRoot":"","sources":["../../src/tools/bash.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAe,cAAc,EAAE,MAAM,qBAAqB,CAAC;AAavE,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAmEjD,wBAAgB,cAAc,CAAC,GAAG,EAAE,aAAa,GAAG,cAAc,CAuQjE;AAED,wBAAgB,oBAAoB,CAAC,GAAG,EAAE,aAAa,GAAG,cAAc,CAyBvE;AAED,wBAAgB,kBAAkB,CAAC,GAAG,EAAE,aAAa,GAAG,cAAc,CAuBrE"}
1
+ {"version":3,"file":"bash.d.ts","sourceRoot":"","sources":["../../src/tools/bash.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAe,cAAc,EAAc,MAAM,qBAAqB,CAAC;AAYnF,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAiFjD,wBAAgB,cAAc,CAAC,GAAG,EAAE,aAAa,GAAG,cAAc,CAsQjE;AAMD,wBAAgB,oBAAoB,CAAC,GAAG,EAAE,aAAa,GAAG,cAAc,CAyBvE;AAED,wBAAgB,kBAAkB,CAAC,GAAG,EAAE,aAAa,GAAG,cAAc,CAuBrE"}
@@ -1 +1 @@
1
- {"version":3,"file":"hoisted.d.ts","sourceRoot":"","sources":["../../src/tools/hoisted.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAKH,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC;AAK1D,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AA8EjD,wEAAwE;AACxE,eAAO,MAAM,wBAAwB,GAAI,IAAI,MAAM,EAAE,QAAQ,MAAM,EAAE,OAAO,MAAM,KAAG,MAChD,CAAC;AA0RtC;;GAEG;AACH,wBAAgB,cAAc,CAAC,GAAG,EAAE,aAAa,GAAG,cAAc,CAmKjE;AAknCD;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,YAAY,CAAC,GAAG,EAAE,aAAa,GAAG,MAAM,CAAC,MAAM,EAAE,cAAc,CAAC,CA0B/E;AAMD;;GAEG;AACH,wBAAgB,gBAAgB,CAAC,GAAG,EAAE,aAAa,GAAG,MAAM,CAAC,MAAM,EAAE,cAAc,CAAC,CA+FnF"}
1
+ {"version":3,"file":"hoisted.d.ts","sourceRoot":"","sources":["../../src/tools/hoisted.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAKH,OAAO,KAAK,EAAE,cAAc,EAAc,MAAM,qBAAqB,CAAC;AAItE,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAwEjD,wEAAwE;AACxE,eAAO,MAAM,wBAAwB,GAAI,IAAI,MAAM,EAAE,QAAQ,MAAM,EAAE,OAAO,MAAM,KAAG,MAChD,CAAC;AA0RtC;;GAEG;AACH,wBAAgB,cAAc,CAAC,GAAG,EAAE,aAAa,GAAG,cAAc,CAwJjE;AA2mCD;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,YAAY,CAAC,GAAG,EAAE,aAAa,GAAG,MAAM,CAAC,MAAM,EAAE,cAAc,CAAC,CA0B/E;AAMD;;GAEG;AACH,wBAAgB,gBAAgB,CAAC,GAAG,EAAE,aAAa,GAAG,MAAM,CAAC,MAAM,EAAE,cAAc,CAAC,CA+FnF"}
@@ -1 +1 @@
1
- {"version":3,"file":"navigation.d.ts","sourceRoot":"","sources":["../../src/tools/navigation.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC;AAE1D,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAYjD;;GAEG;AACH,wBAAgB,eAAe,CAAC,GAAG,EAAE,aAAa,GAAG,MAAM,CAAC,MAAM,EAAE,cAAc,CAAC,CAwFlF"}
1
+ {"version":3,"file":"navigation.d.ts","sourceRoot":"","sources":["../../src/tools/navigation.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC;AAE1D,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAqBjD;;GAEG;AACH,wBAAgB,eAAe,CAAC,GAAG,EAAE,aAAa,GAAG,MAAM,CAAC,MAAM,EAAE,cAAc,CAAC,CAkGlF"}
@@ -1 +1 @@
1
- {"version":3,"file":"reading.d.ts","sourceRoot":"","sources":["../../src/tools/reading.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAe,cAAc,EAAE,MAAM,qBAAqB,CAAC;AAGvE,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AA0CjD,UAAU,qBAAqB;IAC7B,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,OAAO,CAAC;IACjB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED,UAAU,eAAe;IACvB,QAAQ,EAAE,OAAO,CAAC;IAClB,OAAO,EAAE,qBAAqB,EAAE,CAAC;IACjC,IAAI,EAAE,MAAM,CAAC;CACd;AAED;;GAEG;AACH,wBAAgB,YAAY,CAAC,GAAG,EAAE,aAAa,GAAG,MAAM,CAAC,MAAM,EAAE,cAAc,CAAC,CAiX/E;AAED;;;;;;GAMG;AACH,wBAAgB,qBAAqB,CACnC,WAAW,EAAE,MAAM,EACnB,OAAO,EAAE,MAAM,EAAE,EACjB,SAAS,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,GACnC,eAAe,CAyBjB;AA+CD,wBAAgB,sBAAsB,CAAC,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,MAAM,CA2ChF"}
1
+ {"version":3,"file":"reading.d.ts","sourceRoot":"","sources":["../../src/tools/reading.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAe,cAAc,EAAc,MAAM,qBAAqB,CAAC;AAEnF,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAoCjD,UAAU,qBAAqB;IAC7B,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,OAAO,CAAC;IACjB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED,UAAU,eAAe;IACvB,QAAQ,EAAE,OAAO,CAAC;IAClB,OAAO,EAAE,qBAAqB,EAAE,CAAC;IACjC,IAAI,EAAE,MAAM,CAAC;CACd;AAED;;GAEG;AACH,wBAAgB,YAAY,CAAC,GAAG,EAAE,aAAa,GAAG,MAAM,CAAC,MAAM,EAAE,cAAc,CAAC,CAqX/E;AAED;;;;;;GAMG;AACH,wBAAgB,qBAAqB,CACnC,WAAW,EAAE,MAAM,EACnB,OAAO,EAAE,MAAM,EAAE,EACjB,SAAS,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,GACnC,eAAe,CAyBjB;AA+CD,wBAAgB,sBAAsB,CAAC,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,MAAM,CA2ChF"}
@@ -1 +1 @@
1
- {"version":3,"file":"safety.d.ts","sourceRoot":"","sources":["../../src/tools/safety.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAe,cAAc,EAAE,MAAM,qBAAqB,CAAC;AAEvE,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAwCjD;;;GAGG;AACH,wBAAgB,WAAW,CAAC,GAAG,EAAE,aAAa,GAAG,MAAM,CAAC,MAAM,EAAE,cAAc,CAAC,CA+I9E"}
1
+ {"version":3,"file":"safety.d.ts","sourceRoot":"","sources":["../../src/tools/safety.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAe,cAAc,EAAE,MAAM,qBAAqB,CAAC;AAEvE,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAwCjD;;;GAGG;AACH,wBAAgB,WAAW,CAAC,GAAG,EAAE,aAAa,GAAG,MAAM,CAAC,MAAM,EAAE,cAAc,CAAC,CAkJ9E"}
package/dist/tui.js CHANGED
@@ -1,85 +1,61 @@
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.35.4";
4
+ var version = "0.36.1";
5
5
 
6
6
  // src/shared/rpc-client.ts
7
7
  import { existsSync, readdirSync, readFileSync, unlinkSync } from "node:fs";
8
- import { join as join3 } from "node:path";
9
-
10
- // src/logger.ts
11
- import * as fs from "node:fs";
12
- import * as os from "node:os";
13
- import * as path from "node:path";
14
- var TAG = "[aft-plugin]";
15
- var isTestEnv = process.env.BUN_TEST === "1" || false;
16
- var logFile = path.join(os.tmpdir(), isTestEnv ? "aft-plugin-test.log" : "aft-plugin.log");
17
- var useStderr = process.env.AFT_LOG_STDERR === "1";
18
- var buffer = [];
19
- var flushTimer = null;
20
- var FLUSH_INTERVAL_MS = 500;
21
- var BUFFER_SIZE_LIMIT = 50;
22
- function flush() {
23
- if (buffer.length === 0)
24
- return;
25
- const data = buffer.join("");
26
- buffer = [];
27
- try {
28
- if (useStderr) {
29
- process.stderr.write(data);
30
- } else {
31
- fs.appendFileSync(logFile, data);
32
- }
33
- } catch {}
34
- }
35
- function scheduleFlush() {
36
- if (flushTimer)
37
- return;
38
- flushTimer = setTimeout(() => {
39
- flushTimer = null;
40
- flush();
41
- }, FLUSH_INTERVAL_MS);
42
- if (flushTimer && typeof flushTimer === "object" && "unref" in flushTimer) {
43
- flushTimer.unref();
44
- }
45
- }
46
- function write(level, message, data, sessionId) {
47
- try {
48
- const timestamp = new Date().toISOString();
49
- const serialized = data === undefined ? "" : ` ${JSON.stringify(data)}`;
50
- const sessionPrefix = sessionId ? ` [${sessionId}]` : "";
51
- const line = `[${timestamp}] ${level} ${TAG}${sessionPrefix} ${message}${serialized}
52
- `;
53
- if (useStderr) {
54
- process.stderr.write(line);
55
- return;
56
- }
57
- buffer.push(line);
58
- if (buffer.length >= BUFFER_SIZE_LIMIT) {
59
- flush();
60
- } else {
61
- scheduleFlush();
62
- }
63
- } catch {}
64
- }
65
- function warn(message, data) {
66
- write("WARN", message, data);
67
- }
8
+ import { join as join2 } from "node:path";
68
9
 
69
10
  // src/shared/rpc-utils.ts
70
11
  import { createHash } from "node:crypto";
71
- import { join as join2 } from "node:path";
12
+ import { join } from "node:path";
72
13
  function projectHash(directory) {
73
14
  const normalized = directory.replace(/\/+$/, "");
74
15
  return createHash("sha256").update(normalized).digest("hex").slice(0, 16);
75
16
  }
76
17
  function rpcPortFilePath(storageDir, directory) {
77
18
  const hash = projectHash(directory);
78
- return join2(storageDir, "rpc", hash, "port");
19
+ return join(storageDir, "rpc", hash, "port");
79
20
  }
80
21
  function rpcPortFileDir(storageDir, directory) {
81
22
  const hash = projectHash(directory);
82
- return join2(storageDir, "rpc", hash, "ports");
23
+ return join(storageDir, "rpc", hash, "ports");
24
+ }
25
+ function isPidAlive(pid) {
26
+ if (typeof pid !== "number" || !Number.isInteger(pid) || pid <= 0)
27
+ return false;
28
+ try {
29
+ process.kill(pid, 0);
30
+ return true;
31
+ } catch (err) {
32
+ return err.code === "EPERM";
33
+ }
34
+ }
35
+ function parseRpcPortRecord(content) {
36
+ const trimmed = content.trim();
37
+ if (trimmed.length === 0)
38
+ return null;
39
+ if (trimmed.startsWith("{")) {
40
+ try {
41
+ const parsed = JSON.parse(trimmed);
42
+ const port2 = typeof parsed.port === "number" ? parsed.port : Number.NaN;
43
+ if (!Number.isInteger(port2) || port2 <= 0 || port2 > 65535)
44
+ return null;
45
+ return {
46
+ port: port2,
47
+ token: typeof parsed.token === "string" ? parsed.token : null,
48
+ pid: typeof parsed.pid === "number" && Number.isInteger(parsed.pid) ? parsed.pid : undefined,
49
+ started_at: typeof parsed.started_at === "number" ? parsed.started_at : undefined
50
+ };
51
+ } catch {
52
+ return null;
53
+ }
54
+ }
55
+ const port = Number.parseInt(trimmed, 10);
56
+ if (!Number.isInteger(port) || port <= 0 || port > 65535)
57
+ return null;
58
+ return { port, token: null };
83
59
  }
84
60
 
85
61
  // src/shared/rpc-client.ts
@@ -185,7 +161,7 @@ class AftRpcClient {
185
161
  const alive = [];
186
162
  for (const info of infos) {
187
163
  throwIfAborted(signal);
188
- if (await this.healthCheck(info.port, signal)) {
164
+ if (await this.healthCheck(info.port, info.pid, signal)) {
189
165
  this.clearPortFailure(info);
190
166
  alive.push(info);
191
167
  } else {
@@ -212,15 +188,23 @@ class AftRpcClient {
212
188
  };
213
189
  if (existsSync(this.portsDir)) {
214
190
  try {
215
- const entries = readdirSync(this.portsDir).sort();
216
- for (const entry of entries) {
191
+ const live = [];
192
+ for (const entry of readdirSync(this.portsDir)) {
217
193
  if (!entry.endsWith(".json"))
218
194
  continue;
219
- const filePath = join3(this.portsDir, entry);
195
+ const filePath = join2(this.portsDir, entry);
220
196
  const info = this.parsePortFile(filePath);
221
- if (info)
222
- add({ ...info, source: "instance", path: filePath });
197
+ if (!info)
198
+ continue;
199
+ if (info.pid !== undefined && !isPidAlive(info.pid)) {
200
+ this.reclaimDeadPortFile(filePath);
201
+ continue;
202
+ }
203
+ live.push({ ...info, source: "instance", path: filePath });
223
204
  }
205
+ live.sort((a, b) => (b.started_at ?? 0) - (a.started_at ?? 0) || (b.pid ?? 0) - (a.pid ?? 0) || (b.port ?? 0) - (a.port ?? 0));
206
+ for (const info of live)
207
+ add(info);
224
208
  } catch {}
225
209
  }
226
210
  const legacyInfo = this.parsePortFile(this.legacyPortFile);
@@ -228,24 +212,15 @@ class AftRpcClient {
228
212
  add({ ...legacyInfo, source: "legacy", path: this.legacyPortFile });
229
213
  return collected;
230
214
  }
215
+ reclaimDeadPortFile(filePath) {
216
+ try {
217
+ unlinkSync(filePath);
218
+ } catch {}
219
+ }
231
220
  parsePortFile(filePath) {
232
221
  try {
233
- const content = readFileSync(filePath, "utf-8").trim();
234
- let port;
235
- let token;
236
- if (content.startsWith("{")) {
237
- const parsed = JSON.parse(content);
238
- port = typeof parsed.port === "number" ? parsed.port : Number.NaN;
239
- token = typeof parsed.token === "string" ? parsed.token : null;
240
- } else {
241
- warn("RPC port file uses legacy integer format; unauthenticated RPC is deprecated");
242
- port = Number.parseInt(content, 10);
243
- token = null;
244
- }
245
- if (Number.isNaN(port) || port <= 0 || port > 65535) {
246
- return null;
247
- }
248
- return { port, token };
222
+ const content = readFileSync(filePath, "utf-8");
223
+ return parseRpcPortRecord(content);
249
224
  } catch {
250
225
  return null;
251
226
  }
@@ -277,10 +252,21 @@ class AftRpcClient {
277
252
  }
278
253
  } catch {}
279
254
  }
280
- async healthCheck(port, signal) {
255
+ async healthCheck(port, expectedPid, signal) {
281
256
  try {
282
257
  const response = await this.fetchWithTimeout(`http://127.0.0.1:${port}/health`, { method: "GET" }, signal);
283
- return response.ok;
258
+ if (!response.ok)
259
+ return false;
260
+ if (expectedPid !== undefined) {
261
+ try {
262
+ const body = await response.json();
263
+ if (typeof body?.pid === "number" && body.pid !== expectedPid)
264
+ return false;
265
+ } catch {
266
+ return false;
267
+ }
268
+ }
269
+ return true;
284
270
  } catch {
285
271
  throwIfAborted(signal);
286
272
  return false;
@@ -454,7 +440,7 @@ import { createEffect, createMemo, createSignal, on, onCleanup } from "solid-js"
454
440
 
455
441
  // src/shared/storage-paths.ts
456
442
  import { homedir } from "node:os";
457
- import { join as join4 } from "node:path";
443
+ import { join as join3 } from "node:path";
458
444
  function homeDir() {
459
445
  if (process.platform === "win32")
460
446
  return process.env.USERPROFILE || process.env.HOME || homedir();
@@ -464,12 +450,12 @@ function dataHome() {
464
450
  if (process.env.XDG_DATA_HOME)
465
451
  return process.env.XDG_DATA_HOME;
466
452
  if (process.platform === "win32") {
467
- return process.env.LOCALAPPDATA || process.env.APPDATA || join4(homeDir(), "AppData", "Local");
453
+ return process.env.LOCALAPPDATA || process.env.APPDATA || join3(homeDir(), "AppData", "Local");
468
454
  }
469
- return join4(homeDir(), ".local", "share");
455
+ return join3(homeDir(), ".local", "share");
470
456
  }
471
457
  function resolveCortexKitStorageRoot() {
472
- return join4(dataHome(), "cortexkit", "aft");
458
+ return join3(dataHome(), "cortexkit", "aft");
473
459
  }
474
460
 
475
461
  // src/tui/sidebar.tsx
@@ -8,6 +8,8 @@ export interface WorkflowHintsOpts {
8
8
  semanticEnabled: boolean;
9
9
  /** `experimental.bash.background` — gates background-bash paragraph. */
10
10
  bashBackgroundEnabled: boolean;
11
+ /** Resolved bash compression flag. */
12
+ bashCompressionEnabled: boolean;
11
13
  /** Set of disabled tool names (after surface filtering). */
12
14
  disabledTools: Set<string>;
13
15
  }
@@ -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,CA+FzE;AAED;;;;;;;GAOG;AACH,wBAAgB,oBAAoB,CAAC,MAAM,EAAE,SAAS,EAAE,aAAa,EAAE,GAAG,CAAC,MAAM,CAAC,GAAG,MAAM,GAAG,IAAI,CAQjG"}
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,sCAAsC;IACtC,sBAAsB,EAAE,OAAO,CAAC;IAChC,4DAA4D;IAC5D,aAAa,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC;CAC5B;AAID;;;;GAIG;AACH,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,iBAAiB,GAAG,MAAM,GAAG,IAAI,CA8GzE;AAED;;;;;;;GAOG;AACH,wBAAgB,oBAAoB,CAAC,MAAM,EAAE,SAAS,EAAE,aAAa,EAAE,GAAG,CAAC,MAAM,CAAC,GAAG,MAAM,GAAG,IAAI,CASjG"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cortexkit/aft-opencode",
3
- "version": "0.35.4",
3
+ "version": "0.36.1",
4
4
  "type": "module",
5
5
  "description": "OpenCode plugin for Agent File Tools (AFT) — tree-sitter and lsp powered code analysis",
6
6
  "main": "dist/index.js",
@@ -30,19 +30,19 @@
30
30
  },
31
31
  "dependencies": {
32
32
  "@clack/prompts": "^1.2.0",
33
- "@cortexkit/aft-bridge": "0.35.4",
33
+ "@cortexkit/aft-bridge": "0.36.1",
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.35.4",
41
- "@cortexkit/aft-darwin-x64": "0.35.4",
42
- "@cortexkit/aft-linux-arm64": "0.35.4",
43
- "@cortexkit/aft-linux-x64": "0.35.4",
44
- "@cortexkit/aft-win32-arm64": "0.35.4",
45
- "@cortexkit/aft-win32-x64": "0.35.4"
40
+ "@cortexkit/aft-darwin-arm64": "0.36.1",
41
+ "@cortexkit/aft-darwin-x64": "0.36.1",
42
+ "@cortexkit/aft-linux-arm64": "0.36.1",
43
+ "@cortexkit/aft-linux-x64": "0.36.1",
44
+ "@cortexkit/aft-win32-arm64": "0.36.1",
45
+ "@cortexkit/aft-win32-x64": "0.36.1"
46
46
  },
47
47
  "devDependencies": {
48
48
  "@opencode-ai/plugin": "^1.15.11",
@@ -1,4 +1,4 @@
1
1
  // Bash-output hint nudges now live in the shared bridge package so both plugin
2
2
  // hosts use one implementation. Re-exported here to keep the existing OpenCode
3
3
  // import path stable.
4
- export { maybeAppendConflictsHint, maybeAppendGrepHint } from "@cortexkit/aft-bridge";
4
+ export { maybeAppendConflictsHint } from "@cortexkit/aft-bridge";
@@ -1,14 +1,13 @@
1
1
  import { existsSync, readdirSync, readFileSync, unlinkSync } from "node:fs";
2
2
  import { join } from "node:path";
3
- import { warn } from "../logger";
4
- import { rpcPortFileDir, rpcPortFilePath } from "./rpc-utils";
3
+ import { isPidAlive, parseRpcPortRecord, rpcPortFileDir, rpcPortFilePath } from "./rpc-utils";
5
4
 
6
5
  const MAX_RETRIES = 10;
7
6
  const RETRY_DELAY_MS = 500;
8
7
  const REQUEST_TIMEOUT_MS = 5000;
9
8
 
10
9
  type PortInfoSource = "instance" | "legacy";
11
- type ParsedPortInfo = { port: number; token: string | null };
10
+ type ParsedPortInfo = { port: number; token: string | null; pid?: number; started_at?: number };
12
11
  type PortInfo = ParsedPortInfo & { source: PortInfoSource; path?: string };
13
12
 
14
13
  export interface AftRpcCallOptions {
@@ -155,7 +154,7 @@ export class AftRpcClient {
155
154
  const alive: PortInfo[] = [];
156
155
  for (const info of infos) {
157
156
  throwIfAborted(signal);
158
- if (await this.healthCheck(info.port, signal)) {
157
+ if (await this.healthCheck(info.port, info.pid, signal)) {
159
158
  this.clearPortFailure(info);
160
159
  alive.push(info);
161
160
  } else {
@@ -180,16 +179,39 @@ export class AftRpcClient {
180
179
  collected.push(info);
181
180
  };
182
181
 
183
- // Per-instance directory (v0.28.2+): one file per plugin load.
182
+ // Per-instance directory (v0.28.2+): one file per plugin load. Each file now
183
+ // records the owning process pid; we skip (and reclaim) any whose process is
184
+ // dead so a poll doesn't wade through crash/restart leftovers, and we order
185
+ // newest-first so the freshest live server wins the port de-dupe (a stale
186
+ // file naming a reused port can no longer mask a fresh one with its old token).
184
187
  if (existsSync(this.portsDir)) {
185
188
  try {
186
- const entries = readdirSync(this.portsDir).sort();
187
- for (const entry of entries) {
189
+ const live: PortInfo[] = [];
190
+ for (const entry of readdirSync(this.portsDir)) {
188
191
  if (!entry.endsWith(".json")) continue;
189
192
  const filePath = join(this.portsDir, entry);
190
193
  const info = this.parsePortFile(filePath);
191
- if (info) add({ ...info, source: "instance", path: filePath });
194
+ if (!info) continue;
195
+ // Only reclaim when we can PROVE the owner is dead (pid present and not
196
+ // alive). Files without a pid (older format) are kept and fall through
197
+ // to the health check, since we can't prove they're dead.
198
+ if (info.pid !== undefined && !isPidAlive(info.pid)) {
199
+ this.reclaimDeadPortFile(filePath);
200
+ continue;
201
+ }
202
+ live.push({ ...info, source: "instance", path: filePath });
192
203
  }
204
+ // Newest first: files with a started_at sort before those without.
205
+ // Break ties deterministically (equal/absent started_at) by pid then
206
+ // port so selection is stable across reads rather than depending on
207
+ // directory iteration order.
208
+ live.sort(
209
+ (a, b) =>
210
+ (b.started_at ?? 0) - (a.started_at ?? 0) ||
211
+ (b.pid ?? 0) - (a.pid ?? 0) ||
212
+ (b.port ?? 0) - (a.port ?? 0),
213
+ );
214
+ for (const info of live) add(info);
193
215
  } catch {
194
216
  // ignore read errors
195
217
  }
@@ -204,24 +226,19 @@ export class AftRpcClient {
204
226
  return collected;
205
227
  }
206
228
 
229
+ /** Delete a port file whose owning process is provably dead (best-effort). */
230
+ private reclaimDeadPortFile(filePath: string): void {
231
+ try {
232
+ unlinkSync(filePath);
233
+ } catch {
234
+ // best-effort; a concurrent writer may have already replaced it
235
+ }
236
+ }
237
+
207
238
  private parsePortFile(filePath: string): ParsedPortInfo | null {
208
239
  try {
209
- const content = readFileSync(filePath, "utf-8").trim();
210
- let port: number;
211
- let token: string | null;
212
- if (content.startsWith("{")) {
213
- const parsed = JSON.parse(content) as { port?: unknown; token?: unknown };
214
- port = typeof parsed.port === "number" ? parsed.port : Number.NaN;
215
- token = typeof parsed.token === "string" ? parsed.token : null;
216
- } else {
217
- warn("RPC port file uses legacy integer format; unauthenticated RPC is deprecated");
218
- port = Number.parseInt(content, 10);
219
- token = null;
220
- }
221
- if (Number.isNaN(port) || port <= 0 || port > 65535) {
222
- return null;
223
- }
224
- return { port, token };
240
+ const content = readFileSync(filePath, "utf-8");
241
+ return parseRpcPortRecord(content);
225
242
  } catch {
226
243
  return null;
227
244
  }
@@ -259,14 +276,34 @@ export class AftRpcClient {
259
276
  }
260
277
  }
261
278
 
262
- private async healthCheck(port: number, signal?: AbortSignal): Promise<boolean> {
279
+ private async healthCheck(
280
+ port: number,
281
+ expectedPid: number | undefined,
282
+ signal?: AbortSignal,
283
+ ): Promise<boolean> {
263
284
  try {
264
285
  const response = await this.fetchWithTimeout(
265
286
  `http://127.0.0.1:${port}/health`,
266
287
  { method: "GET" },
267
288
  signal,
268
289
  );
269
- return response.ok;
290
+ if (!response.ok) return false;
291
+ // Identity check: the port file records the owning pid and `/health`
292
+ // echoes the live server's pid. If they disagree, the port was recycled
293
+ // by a *different* AFT server (or an unrelated localhost service that
294
+ // happens to answer /health), so this port file is stale — reject it so
295
+ // we don't POST this file's token to the wrong server. Only enforced when
296
+ // both sides expose a pid (older port files omit it).
297
+ if (expectedPid !== undefined) {
298
+ try {
299
+ const body = (await response.json()) as { pid?: unknown };
300
+ if (typeof body?.pid === "number" && body.pid !== expectedPid) return false;
301
+ } catch {
302
+ // Non-JSON / unexpected body: not a healthy AFT server we trust.
303
+ return false;
304
+ }
305
+ }
306
+ return true;
270
307
  } catch {
271
308
  throwIfAborted(signal);
272
309
  return false;
@@ -53,10 +53,19 @@ export class AftRpcServer {
53
53
  const dir = dirname(this.portFilePath);
54
54
  mkdirSync(dir, { recursive: true, mode: 0o700 });
55
55
  const tmpPath = `${this.portFilePath}.tmp`;
56
- writeFileSync(tmpPath, JSON.stringify({ port: this.port, token: this.token }), {
57
- encoding: "utf-8",
58
- mode: 0o600,
59
- });
56
+ // Record pid + started_at so the client can skip files whose owning
57
+ // process is dead (without a health-check round-trip) and prefer the
58
+ // freshest live server, instead of accumulating crash/restart leftovers.
59
+ writeFileSync(
60
+ tmpPath,
61
+ JSON.stringify({
62
+ port: this.port,
63
+ token: this.token,
64
+ pid: process.pid,
65
+ started_at: Date.now(),
66
+ }),
67
+ { encoding: "utf-8", mode: 0o600 },
68
+ );
60
69
  renameSync(tmpPath, this.portFilePath);
61
70
  log(`RPC server listening on 127.0.0.1:${this.port}`);
62
71
  } catch (err) {