@f5-sales-demo/xcsh 19.56.0 → 19.56.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"type": "module",
|
|
3
3
|
"name": "@f5-sales-demo/xcsh",
|
|
4
|
-
"version": "19.56.
|
|
4
|
+
"version": "19.56.1",
|
|
5
5
|
"description": "Coding agent CLI with read, bash, edit, write tools and session management",
|
|
6
6
|
"homepage": "https://github.com/f5-sales-demo/xcsh",
|
|
7
7
|
"author": "Can Boluk",
|
|
@@ -55,13 +55,13 @@
|
|
|
55
55
|
"dependencies": {
|
|
56
56
|
"@agentclientprotocol/sdk": "0.16.1",
|
|
57
57
|
"@mozilla/readability": "^0.6",
|
|
58
|
-
"@f5-sales-demo/xcsh-stats": "19.56.
|
|
59
|
-
"@f5-sales-demo/pi-agent-core": "19.56.
|
|
60
|
-
"@f5-sales-demo/pi-ai": "19.56.
|
|
61
|
-
"@f5-sales-demo/pi-natives": "19.56.
|
|
62
|
-
"@f5-sales-demo/pi-resource-management": "19.56.
|
|
63
|
-
"@f5-sales-demo/pi-tui": "19.56.
|
|
64
|
-
"@f5-sales-demo/pi-utils": "19.56.
|
|
58
|
+
"@f5-sales-demo/xcsh-stats": "19.56.1",
|
|
59
|
+
"@f5-sales-demo/pi-agent-core": "19.56.1",
|
|
60
|
+
"@f5-sales-demo/pi-ai": "19.56.1",
|
|
61
|
+
"@f5-sales-demo/pi-natives": "19.56.1",
|
|
62
|
+
"@f5-sales-demo/pi-resource-management": "19.56.1",
|
|
63
|
+
"@f5-sales-demo/pi-tui": "19.56.1",
|
|
64
|
+
"@f5-sales-demo/pi-utils": "19.56.1",
|
|
65
65
|
"@sinclair/typebox": "^0.34",
|
|
66
66
|
"@xterm/headless": "^6.0",
|
|
67
67
|
"ajv": "^8.20",
|
package/src/commands/manager.ts
CHANGED
|
@@ -76,6 +76,50 @@ export function workerArgv(): string[] {
|
|
|
76
76
|
return reexecArgv("worker");
|
|
77
77
|
}
|
|
78
78
|
|
|
79
|
+
/**
|
|
80
|
+
* Acquire the manager control socket, robust across Bun runtimes.
|
|
81
|
+
*
|
|
82
|
+
* The single-manager invariant needs three things at once: (1) never clobber a
|
|
83
|
+
* LIVE manager's socket, (2) reclaim a STALE socket left by a crashed/killed
|
|
84
|
+
* manager, and (3) survive a concurrent cold-start race. Bun's unix `listen` is
|
|
85
|
+
* NOT a reliable oracle here — dev `bun run` silently unlinks-and-rebinds a
|
|
86
|
+
* stale socket, while the COMPILED binary throws EADDRINUSE (xcsh #1846), which
|
|
87
|
+
* previously crashed the manager and silently killed all automation. So we drive
|
|
88
|
+
* it explicitly:
|
|
89
|
+
*
|
|
90
|
+
* 1. Probe for a live owner. If one answers → we lost; bail ("already-live").
|
|
91
|
+
* 2. Try to listen. If it binds → done ("bound").
|
|
92
|
+
* 3. On EADDRINUSE, RE-PROBE: a live answer now means a rival bound between
|
|
93
|
+
* our probe and our listen → bail WITHOUT touching its socket. Otherwise
|
|
94
|
+
* the file is confirmed stale → unlink it and retry listen once. A still-
|
|
95
|
+
* failing retry (or any non-EADDRINUSE error) propagates loudly.
|
|
96
|
+
*
|
|
97
|
+
* Effects are injected so the branch logic is unit-tested deterministically
|
|
98
|
+
* (the compiled-only EADDRINUSE path is unreachable from a dev test otherwise).
|
|
99
|
+
*/
|
|
100
|
+
export async function acquireControlSocket(opts: {
|
|
101
|
+
sockPath: string;
|
|
102
|
+
probeLive: (sockPath: string) => Promise<boolean>;
|
|
103
|
+
listen: () => void;
|
|
104
|
+
unlink: (sockPath: string) => void;
|
|
105
|
+
isAddrInUse: (err: unknown) => boolean;
|
|
106
|
+
}): Promise<"bound" | "already-live"> {
|
|
107
|
+
const { sockPath, probeLive, listen, unlink, isAddrInUse } = opts;
|
|
108
|
+
if (await probeLive(sockPath)) return "already-live";
|
|
109
|
+
try {
|
|
110
|
+
listen();
|
|
111
|
+
return "bound";
|
|
112
|
+
} catch (err) {
|
|
113
|
+
if (!isAddrInUse(err)) throw err;
|
|
114
|
+
// Bind collided. Distinguish a live rival (lost a cold-start race) from a
|
|
115
|
+
// stale file left by a crashed manager.
|
|
116
|
+
if (await probeLive(sockPath)) return "already-live";
|
|
117
|
+
unlink(sockPath); // confirmed stale → reclaim it
|
|
118
|
+
listen(); // retry once; a real failure now propagates
|
|
119
|
+
return "bound";
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
|
|
79
123
|
export default class Manager extends Command {
|
|
80
124
|
static description = "Run the detached control server that spawns/reaps per-tab workers; blocks forever";
|
|
81
125
|
|
|
@@ -180,39 +224,35 @@ export default class Manager extends Command {
|
|
|
180
224
|
},
|
|
181
225
|
};
|
|
182
226
|
|
|
183
|
-
// Single-manager invariant
|
|
184
|
-
//
|
|
185
|
-
//
|
|
186
|
-
//
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
/* nothing accepting → free path, or a stale socket file from a crashed manager */
|
|
210
|
-
}
|
|
211
|
-
if (liveOwner) {
|
|
227
|
+
// Single-manager invariant + stale-socket reclamation. A live manager is
|
|
228
|
+
// never clobbered (we probe first and again on collision); a stale socket
|
|
229
|
+
// from a crashed/killed manager is reclaimed rather than crashing on
|
|
230
|
+
// EADDRINUSE. See acquireControlSocket for the full rationale (xcsh #1846).
|
|
231
|
+
const probeLive = async (p: string): Promise<boolean> => {
|
|
232
|
+
try {
|
|
233
|
+
const probe = await Promise.race([
|
|
234
|
+
Bun.connect({ unix: p, socket: { data() {} } }),
|
|
235
|
+
new Promise<never>((_, reject) =>
|
|
236
|
+
setTimeout(() => reject(new Error("manager liveness probe timeout")), 500),
|
|
237
|
+
),
|
|
238
|
+
]);
|
|
239
|
+
probe.end();
|
|
240
|
+
return true;
|
|
241
|
+
} catch {
|
|
242
|
+
return false; // nothing accepting → free path, or a stale socket file
|
|
243
|
+
}
|
|
244
|
+
};
|
|
245
|
+
const outcome = await acquireControlSocket({
|
|
246
|
+
sockPath,
|
|
247
|
+
probeLive,
|
|
248
|
+
listen: () => Bun.listen(socketConfig),
|
|
249
|
+
unlink: p => fs.rmSync(p, { force: true }),
|
|
250
|
+
isAddrInUse: err => (err as { code?: string } | null)?.code === "EADDRINUSE",
|
|
251
|
+
});
|
|
252
|
+
if (outcome === "already-live") {
|
|
212
253
|
console.error(`[xcsh manager] another manager already live at ${sockPath}; exiting`);
|
|
213
254
|
process.exit(0);
|
|
214
255
|
}
|
|
215
|
-
Bun.listen(socketConfig);
|
|
216
256
|
console.error(`[xcsh manager] control socket listening at ${sockPath}`);
|
|
217
257
|
|
|
218
258
|
// Idle sweep: reap workers untouched for longer than the TTL.
|
|
@@ -17,17 +17,17 @@ export interface BuildInfo {
|
|
|
17
17
|
}
|
|
18
18
|
|
|
19
19
|
export const BUILD_INFO: BuildInfo = {
|
|
20
|
-
"version": "19.56.
|
|
21
|
-
"commit": "
|
|
22
|
-
"shortCommit": "
|
|
20
|
+
"version": "19.56.1",
|
|
21
|
+
"commit": "5630d1694415b3da8edb73803fdf7ad37467d77d",
|
|
22
|
+
"shortCommit": "5630d16",
|
|
23
23
|
"branch": "main",
|
|
24
|
-
"tag": "v19.56.
|
|
25
|
-
"commitDate": "2026-07-
|
|
26
|
-
"buildDate": "2026-07-
|
|
24
|
+
"tag": "v19.56.1",
|
|
25
|
+
"commitDate": "2026-07-03T17:49:52Z",
|
|
26
|
+
"buildDate": "2026-07-03T18:14:31.834Z",
|
|
27
27
|
"dirty": true,
|
|
28
28
|
"prNumber": "",
|
|
29
29
|
"repoUrl": "https://github.com/f5-sales-demo/xcsh",
|
|
30
30
|
"repoSlug": "f5-sales-demo/xcsh",
|
|
31
|
-
"commitUrl": "https://github.com/f5-sales-demo/xcsh/commit/
|
|
32
|
-
"releaseUrl": "https://github.com/f5-sales-demo/xcsh/releases/tag/v19.56.
|
|
31
|
+
"commitUrl": "https://github.com/f5-sales-demo/xcsh/commit/5630d1694415b3da8edb73803fdf7ad37467d77d",
|
|
32
|
+
"releaseUrl": "https://github.com/f5-sales-demo/xcsh/releases/tag/v19.56.1"
|
|
33
33
|
};
|