@zswarm/core 0.4.0 → 0.4.2
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/host/crew.js +30 -12
- package/dist/ops/delivery.d.ts +7 -3
- package/dist/ops/delivery.js +13 -9
- package/dist/ops/restart.js +4 -0
- package/dist/schema.js +1 -1
- package/dist/zellij/bus.js +1 -0
- package/dist/zellij/panes.d.ts +2 -0
- package/dist/zellij/panes.js +1 -0
- package/package.json +1 -1
package/dist/host/crew.js
CHANGED
|
@@ -116,6 +116,10 @@ function foreignLayoutHint(session) {
|
|
|
116
116
|
`(on Windows often a renamed session whose server kept its old name). Stop the server that holds it ` +
|
|
117
117
|
`(a zellij.exe --server process), run \`zellij delete-session ${session}\`, then \`zswarm crew start ${session}\``);
|
|
118
118
|
}
|
|
119
|
+
function liveSessionHint(session) {
|
|
120
|
+
return (`If that session should go, stop it yourself (\`zellij kill-session ${session}\`; on Windows a renamed ` +
|
|
121
|
+
`session's server may have to be stopped by its PID), then run \`zswarm crew start ${session}\` again.`);
|
|
122
|
+
}
|
|
119
123
|
function crewRecorder(deps, env) {
|
|
120
124
|
const record = deps.record ?? ((entry) => createStateStore({ env }).appendLog(entry));
|
|
121
125
|
return (session, ok, detail) => {
|
|
@@ -205,25 +209,39 @@ export async function crewStart(session, deps = {}) {
|
|
|
205
209
|
const record = crewRecorder(deps, env0);
|
|
206
210
|
const names = layoutPaneNames(readFileSync(crewFile(dir, files.layout), "utf8"));
|
|
207
211
|
const config = files.config ? ["--config", crewFile(dir, files.config)] : [];
|
|
212
|
+
// One shared pre-check for both platforms: a live session that already has
|
|
213
|
+
// the crew's layout is the crew — keep it, never rebuild or kill it. A live
|
|
214
|
+
// session with another layout is not ours to stop; refuse and stop nothing.
|
|
215
|
+
const client = account.platform === "win32" ? windowsClient(deps, env) : null;
|
|
216
|
+
if ((await sessions(env)).includes(session)) {
|
|
217
|
+
const live = checkLayout(await panes(session, env), files.first, names);
|
|
218
|
+
if (live.state === "ok") {
|
|
219
|
+
if (client) {
|
|
220
|
+
const opened = await client.ensure(session);
|
|
221
|
+
log(`session ${session} already up; client window ${opened.opened ? "opened" : "already open"}`);
|
|
222
|
+
}
|
|
223
|
+
else {
|
|
224
|
+
log(`session ${session} already up with its layout`);
|
|
225
|
+
}
|
|
226
|
+
return 0;
|
|
227
|
+
}
|
|
228
|
+
// Live but not the crew's layout (state "foreign" or "missing"): refuse.
|
|
229
|
+
const why = `refused: a live session named ${session} is already running (${live.seen}) without this crew's layout; nothing was stopped. ${liveSessionHint(session)}`;
|
|
230
|
+
log(`session ${session} ${why}`);
|
|
231
|
+
record(session, false, why);
|
|
232
|
+
return 1;
|
|
233
|
+
}
|
|
208
234
|
if (account.platform === "win32") {
|
|
209
235
|
// Windows: a session created with `attach --create-background` renders
|
|
210
236
|
// empty panes and drops pasted input, so the session is created BY an
|
|
211
237
|
// attached client instead — a minimized console window running
|
|
212
238
|
// `zellij --layout ... attach --create`, which `crew client` starts. That
|
|
213
239
|
// window is what `crew start` reopens when it is gone.
|
|
214
|
-
const
|
|
215
|
-
// A live session that already has its layout is the crew: keep it and only
|
|
216
|
-
// make sure its window is there. Rebuilding would restart every pane.
|
|
217
|
-
if ((await sessions(env)).includes(session)
|
|
218
|
-
&& checkLayout(await panes(session, env), files.first, names).state === "ok") {
|
|
219
|
-
const opened = await client.ensure(session);
|
|
220
|
-
log(`session ${session} already up; client window ${opened.opened ? "opened" : "already open"}`);
|
|
221
|
-
return 0;
|
|
222
|
-
}
|
|
240
|
+
const winClient = client ?? windowsClient(deps, env);
|
|
223
241
|
for (let attempt = 1; attempt <= 3; attempt++) {
|
|
224
242
|
zellij(["delete-session", session], env); // clears only a dead session of this name
|
|
225
|
-
await
|
|
226
|
-
const opened = await
|
|
243
|
+
await winClient.close(session); // a window left over from the previous attempt
|
|
244
|
+
const opened = await winClient.ensure(session);
|
|
227
245
|
log(`client window ${opened.opened ? "opened" : "already open"} for ${session}`);
|
|
228
246
|
for (let i = 0; i < 30 && !(await sessions(env)).includes(session); i++)
|
|
229
247
|
await sleep(1_000);
|
|
@@ -234,7 +252,7 @@ export async function crewStart(session, deps = {}) {
|
|
|
234
252
|
return 0;
|
|
235
253
|
}
|
|
236
254
|
zellij(["kill-session", session], env);
|
|
237
|
-
await
|
|
255
|
+
await winClient.close(session);
|
|
238
256
|
if (check.state === "foreign") {
|
|
239
257
|
const why = `stopped: came up with ${check.seen}, none from its layout. ${foreignLayoutHint(session)}`;
|
|
240
258
|
log(`session ${session} ${why}`);
|
package/dist/ops/delivery.d.ts
CHANGED
|
@@ -21,8 +21,9 @@ export type SenderOrigin = {
|
|
|
21
21
|
};
|
|
22
22
|
/**
|
|
23
23
|
* Clean one origin part for the `[zswarm from=…]` header: non-strings and
|
|
24
|
-
* blanks become null, header-breaking characters
|
|
25
|
-
*
|
|
24
|
+
* blanks become null, header-breaking characters and whitespace runs become a
|
|
25
|
+
* single `_` (a value never holds a space, so `key=value` pairs stay
|
|
26
|
+
* splittable), and the result is capped at 64 chars.
|
|
26
27
|
*/
|
|
27
28
|
export declare function sanitizeOriginPart(raw: unknown): string | null;
|
|
28
29
|
/** The origin of the caller's own process, read from its environment. */
|
|
@@ -33,7 +34,10 @@ export declare function resolveSenderOrigin(env: NodeJS.ProcessEnv): SenderOrigi
|
|
|
33
34
|
* client) keeps every part null but still records that serve carried it.
|
|
34
35
|
*/
|
|
35
36
|
export declare function originFromServeRequest(raw: unknown): SenderOrigin;
|
|
36
|
-
/**
|
|
37
|
+
/**
|
|
38
|
+
* " src=session/pane host=host via=serve" (only the parts that are known).
|
|
39
|
+
* `from=` stays the bare label, so a reader can split the header on spaces.
|
|
40
|
+
*/
|
|
37
41
|
export declare function originSuffix(origin: SenderOrigin | null | undefined): string;
|
|
38
42
|
export type DeliveryResult = {
|
|
39
43
|
to: string;
|
package/dist/ops/delivery.js
CHANGED
|
@@ -6,18 +6,19 @@ import { isTrue, numberArg, optionalString } from "./util.js";
|
|
|
6
6
|
const ORIGIN_PART_MAX = 64;
|
|
7
7
|
/**
|
|
8
8
|
* Clean one origin part for the `[zswarm from=…]` header: non-strings and
|
|
9
|
-
* blanks become null, header-breaking characters
|
|
10
|
-
*
|
|
9
|
+
* blanks become null, header-breaking characters and whitespace runs become a
|
|
10
|
+
* single `_` (a value never holds a space, so `key=value` pairs stay
|
|
11
|
+
* splittable), and the result is capped at 64 chars.
|
|
11
12
|
*/
|
|
12
13
|
export function sanitizeOriginPart(raw) {
|
|
13
14
|
if (typeof raw !== "string")
|
|
14
15
|
return null;
|
|
15
|
-
const cleaned = raw.replace(/[\r\n[\]]+/g, " ").replace(
|
|
16
|
+
const cleaned = raw.replace(/[\r\n[\]\s]+/g, " ").trim().replace(/ /g, "_");
|
|
16
17
|
if (!cleaned)
|
|
17
18
|
return null;
|
|
18
19
|
return cleaned.length <= ORIGIN_PART_MAX
|
|
19
20
|
? cleaned
|
|
20
|
-
: cleaned.slice(0, ORIGIN_PART_MAX).
|
|
21
|
+
: cleaned.slice(0, ORIGIN_PART_MAX).replace(/_+$/, "");
|
|
21
22
|
}
|
|
22
23
|
/** The origin of the caller's own process, read from its environment. */
|
|
23
24
|
export function resolveSenderOrigin(env) {
|
|
@@ -50,15 +51,18 @@ export function originFromServeRequest(raw) {
|
|
|
50
51
|
via: "serve",
|
|
51
52
|
};
|
|
52
53
|
}
|
|
53
|
-
/**
|
|
54
|
+
/**
|
|
55
|
+
* " src=session/pane host=host via=serve" (only the parts that are known).
|
|
56
|
+
* `from=` stays the bare label, so a reader can split the header on spaces.
|
|
57
|
+
*/
|
|
54
58
|
export function originSuffix(origin) {
|
|
55
59
|
if (!origin)
|
|
56
60
|
return "";
|
|
57
61
|
const { session, pane, host, via } = origin;
|
|
58
|
-
const
|
|
59
|
-
return ((
|
|
60
|
-
(host ? "
|
|
61
|
-
(via === "serve" ? " via
|
|
62
|
+
const src = session && pane ? `${session}/${pane}` : (pane ?? session ?? null);
|
|
63
|
+
return ((src ? " src=" + src : "") +
|
|
64
|
+
(host ? " host=" + host : "") +
|
|
65
|
+
(via === "serve" ? " via=serve" : ""));
|
|
62
66
|
}
|
|
63
67
|
/**
|
|
64
68
|
* An args-supplied origin, cleaned again (a direct caller may skip dispatch),
|
package/dist/ops/restart.js
CHANGED
|
@@ -174,6 +174,10 @@ async function waitForExit(client, session, pane, kind, profile, budgetMs, clock
|
|
|
174
174
|
const live = panes.find((p) => p.id === pane.id);
|
|
175
175
|
if (live?.exited)
|
|
176
176
|
return { exited: true, reason: "pane_exited" };
|
|
177
|
+
// A held command pane is Zellij's "exited, press Enter to re-run" state;
|
|
178
|
+
// the relaunch step already presses Enter for command panes.
|
|
179
|
+
if (kind === "command" && live?.held)
|
|
180
|
+
return { exited: true, reason: "pane_held" };
|
|
177
181
|
}
|
|
178
182
|
catch {
|
|
179
183
|
// A transient listing failure is not evidence the pane is gone.
|
package/dist/schema.js
CHANGED
|
@@ -281,7 +281,7 @@ export const PARAMS = [
|
|
|
281
281
|
type: "string",
|
|
282
282
|
flags: ["--from", "-f"],
|
|
283
283
|
ops: ["send", "broadcast"],
|
|
284
|
-
description: "send: sender label in the [zswarm from=…] prefix (default: ZSWARM_FROM, else the sending pane's title, else swarm). zswarm appends
|
|
284
|
+
description: "send: sender label in the [zswarm from=…] prefix (default: ZSWARM_FROM, else the sending pane's title, else swarm). zswarm appends src=<session>/<pane>, host=<host> (ZSWARM_HOST) and via=serve",
|
|
285
285
|
},
|
|
286
286
|
{
|
|
287
287
|
name: "raw",
|
package/dist/zellij/bus.js
CHANGED
package/dist/zellij/panes.d.ts
CHANGED
package/dist/zellij/panes.js
CHANGED
|
@@ -31,6 +31,7 @@ function parsePaneRow(row) {
|
|
|
31
31
|
tabId: typeof row.tab_id === "number" ? row.tab_id : null,
|
|
32
32
|
focused: row.is_focused === true,
|
|
33
33
|
exited: row.exited === true,
|
|
34
|
+
held: row.is_held === true,
|
|
34
35
|
floating: row.is_floating === true,
|
|
35
36
|
contentColumns: typeof row.pane_content_columns === "number"
|
|
36
37
|
? row.pane_content_columns
|