@zswarm/core 0.4.1 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/host/crew.js +30 -12
- package/dist/ops/dispatch.js +1 -1
- package/dist/ops/restart.js +4 -0
- package/dist/ops/signals.d.ts +1 -1
- package/dist/ops/signals.js +24 -11
- package/dist/schema.js +6 -6
- 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/dispatch.js
CHANGED
|
@@ -646,7 +646,7 @@ async function dispatchOperation(args, injected, deps, context, env) {
|
|
|
646
646
|
case "signal":
|
|
647
647
|
return postSignal(state(), args, clock);
|
|
648
648
|
case "signals":
|
|
649
|
-
return listSignals(state());
|
|
649
|
+
return listSignals(state(), args);
|
|
650
650
|
case "await":
|
|
651
651
|
return await awaitSignal(state(), args, clock);
|
|
652
652
|
case "log":
|
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/ops/signals.d.ts
CHANGED
|
@@ -5,6 +5,6 @@ import type { Clock, OpsResult } from "./types.js";
|
|
|
5
5
|
* crew can rendezvous without any of them staying resident.
|
|
6
6
|
*/
|
|
7
7
|
export declare function postSignal(state: StateStore, args: Record<string, unknown>, clock: Clock): OpsResult;
|
|
8
|
-
export declare function listSignals(state: StateStore): OpsResult;
|
|
8
|
+
export declare function listSignals(state: StateStore, args?: Record<string, unknown>): OpsResult;
|
|
9
9
|
/** Block until a channel reaches `count` posts, or the deadline passes. */
|
|
10
10
|
export declare function awaitSignal(state: StateStore, args: Record<string, unknown>, clock: Clock): Promise<OpsResult>;
|
package/dist/ops/signals.js
CHANGED
|
@@ -28,20 +28,33 @@ export function postSignal(state, args, clock) {
|
|
|
28
28
|
data: { channel, count: posted.count, at: posted.at, payload: posted.last },
|
|
29
29
|
};
|
|
30
30
|
}
|
|
31
|
-
export function listSignals(state) {
|
|
31
|
+
export function listSignals(state, args = {}) {
|
|
32
32
|
const all = state.readSignals();
|
|
33
|
+
const total = Object.keys(all).length;
|
|
34
|
+
const channel = optionalString(args.channel);
|
|
35
|
+
const since = optionalString(args.since);
|
|
36
|
+
const sinceAt = since ? Number(since) : null;
|
|
37
|
+
const hasLimit = args.limit !== undefined && args.limit !== null && args.limit !== "";
|
|
38
|
+
let matched = Object.entries(all).map(([name, v]) => ({
|
|
39
|
+
channel: name,
|
|
40
|
+
count: v.count,
|
|
41
|
+
at: v.at,
|
|
42
|
+
last: v.last ?? null,
|
|
43
|
+
}));
|
|
44
|
+
if (channel)
|
|
45
|
+
matched = matched.filter((c) => c.channel.startsWith(channel));
|
|
46
|
+
if (sinceAt !== null && Number.isFinite(sinceAt)) {
|
|
47
|
+
matched = matched.filter((c) => c.at >= sinceAt);
|
|
48
|
+
}
|
|
49
|
+
const matchedCount = matched.length;
|
|
50
|
+
const channels = hasLimit
|
|
51
|
+
? matched
|
|
52
|
+
.sort((a, b) => b.at - a.at)
|
|
53
|
+
.slice(0, numberArg(args, "limit", 1, { min: 1, max: 1_000 }))
|
|
54
|
+
: matched.sort((a, b) => a.channel.localeCompare(b.channel));
|
|
33
55
|
return {
|
|
34
56
|
ok: true,
|
|
35
|
-
data: {
|
|
36
|
-
channels: Object.entries(all)
|
|
37
|
-
.map(([channel, v]) => ({
|
|
38
|
-
channel,
|
|
39
|
-
count: v.count,
|
|
40
|
-
at: v.at,
|
|
41
|
-
last: v.last ?? null,
|
|
42
|
-
}))
|
|
43
|
-
.sort((a, b) => a.channel.localeCompare(b.channel)),
|
|
44
|
-
},
|
|
57
|
+
data: { total, matched: matchedCount, channels },
|
|
45
58
|
};
|
|
46
59
|
}
|
|
47
60
|
/** Block until a channel reaches `count` posts, or the deadline passes. */
|
package/dist/schema.js
CHANGED
|
@@ -141,8 +141,8 @@ export const PARAMS = [
|
|
|
141
141
|
name: "channel",
|
|
142
142
|
type: "string",
|
|
143
143
|
flags: ["--channel"],
|
|
144
|
-
ops: ["signal", "await"],
|
|
145
|
-
description: "signal/await: channel name",
|
|
144
|
+
ops: ["signal", "await", "signals"],
|
|
145
|
+
description: "signal/await: channel name; signals: only channels whose name starts with this",
|
|
146
146
|
},
|
|
147
147
|
{
|
|
148
148
|
name: "payload",
|
|
@@ -216,15 +216,15 @@ export const PARAMS = [
|
|
|
216
216
|
name: "limit",
|
|
217
217
|
type: "number",
|
|
218
218
|
flags: ["--limit"],
|
|
219
|
-
ops: ["log"],
|
|
220
|
-
description: "log: how many entries to return (default 20)",
|
|
219
|
+
ops: ["log", "signals"],
|
|
220
|
+
description: "log: how many entries to return (default 20); signals: the N most recently updated channels",
|
|
221
221
|
},
|
|
222
222
|
{
|
|
223
223
|
name: "since",
|
|
224
224
|
type: "string",
|
|
225
225
|
flags: ["--since"],
|
|
226
|
-
ops: ["log"],
|
|
227
|
-
description: "log: only entries at or after this epoch millisecond",
|
|
226
|
+
ops: ["log", "signals"],
|
|
227
|
+
description: "log: only entries at or after this epoch millisecond; signals: only channels updated at or after this epoch millisecond",
|
|
228
228
|
},
|
|
229
229
|
{
|
|
230
230
|
name: "failed",
|
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
|