@zswarm/core 0.5.0 → 0.7.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/cli.js +5 -1
- package/dist/host/relay.d.ts +7 -0
- package/dist/host/relay.js +32 -2
- package/dist/index.d.ts +5 -4
- package/dist/index.js +4 -3
- package/dist/ops/delivery.d.ts +6 -2
- package/dist/ops/delivery.js +7 -3
- package/dist/ops/dispatch.js +32 -1
- package/dist/ops/panes.d.ts +2 -1
- package/dist/ops/panes.js +15 -3
- package/dist/schema.js +7 -0
- package/dist/state.d.ts +16 -0
- package/dist/state.js +50 -0
- package/dist/zellij/args.d.ts +2 -0
- package/dist/zellij/args.js +4 -0
- package/dist/zellij/client.d.ts +30 -2
- package/dist/zellij/client.js +434 -8
- package/dist/zellij/panes.d.ts +1 -1
- package/dist/zellij/panes.js +8 -3
- package/dist/zellij/server-identity.d.ts +95 -0
- package/dist/zellij/server-identity.js +405 -0
- package/dist/zellij/session.d.ts +3 -1
- package/package.json +1 -1
package/dist/host/cli.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { readFileSync } from "node:fs";
|
|
2
2
|
import { ZellijError } from "../errors.js";
|
|
3
|
-
import { createStateStore } from "../state.js";
|
|
3
|
+
import { createStateStore, defaultStateDir } from "../state.js";
|
|
4
4
|
import { crewCheck, crewClient, crewDown, crewStart, crewStatus, crewUp } from "./crew.js";
|
|
5
5
|
import { hostDoctor, hostInstall, hostInstallArgs, hostScriptPath } from "./host.js";
|
|
6
6
|
import { listLeases } from "./lease.js";
|
|
@@ -151,6 +151,10 @@ async function relay(argv) {
|
|
|
151
151
|
if (flags["--json"] === true)
|
|
152
152
|
return json(() => ({ leases }));
|
|
153
153
|
process.stdout.write(`${leaseTable(leases)}\n`);
|
|
154
|
+
if (leases.length === 0) {
|
|
155
|
+
// A tool that runs zswarm with its own ZSWARM_STATE_DIR keeps its leases there.
|
|
156
|
+
process.stderr.write(`no live leases in ${defaultStateDir()}; set ZSWARM_STATE_DIR to read another state directory\n`);
|
|
157
|
+
}
|
|
154
158
|
return 0;
|
|
155
159
|
}
|
|
156
160
|
const cancelId = str(flags["--cancel"]);
|
package/dist/host/relay.d.ts
CHANGED
|
@@ -46,6 +46,9 @@ export type RelayConfig = {
|
|
|
46
46
|
to: string;
|
|
47
47
|
done: string;
|
|
48
48
|
replyTo: string;
|
|
49
|
+
/** The reply pane's unique title when `replyTo` was a pane id: delivery tries
|
|
50
|
+
* the name first, because ids shift when panes are restarted or renumbered. */
|
|
51
|
+
replyToName?: string;
|
|
49
52
|
replySession?: string;
|
|
50
53
|
from: string;
|
|
51
54
|
/** The target harness (`--harness`); sent with the body and the reply. */
|
|
@@ -112,6 +115,10 @@ export declare function fillMessage(template: string, fields: Record<string, str
|
|
|
112
115
|
* the verdict and keeps waiting — the done line is the real proof of delivery.
|
|
113
116
|
*/
|
|
114
117
|
export declare function softSendVerdict(res: OpsResult): boolean;
|
|
118
|
+
/** The unique title of the reply pane when `replyTo` is a pane id (`terminal_14`, `14`),
|
|
119
|
+
* else undefined: an untitled pane (default shell title) or a title shared by two panes
|
|
120
|
+
* keeps delivery by id. */
|
|
121
|
+
export declare function replyPaneName(cfg: Pick<RelayConfig, "replyTo" | "replySession">, env: NodeJS.ProcessEnv, call: NonNullable<RelayDeps["call"]>): Promise<string | undefined>;
|
|
115
122
|
/** Paste `body` into the reply pane, retrying with backoff; false leaves it in undelivered/. */
|
|
116
123
|
export declare function deliverMessage(cfg: RelayConfig, body: string, dir: string, deps?: RelayDeps): Promise<boolean>;
|
|
117
124
|
/** The waiting loop; returns the final status and the message it delivered or left behind. */
|
package/dist/host/relay.js
CHANGED
|
@@ -111,6 +111,28 @@ export function softSendVerdict(res) {
|
|
|
111
111
|
const submitted = res.data?.submitted;
|
|
112
112
|
return submitted !== true;
|
|
113
113
|
}
|
|
114
|
+
/** The unique title of the reply pane when `replyTo` is a pane id (`terminal_14`, `14`),
|
|
115
|
+
* else undefined: an untitled pane (default shell title) or a title shared by two panes
|
|
116
|
+
* keeps delivery by id. */
|
|
117
|
+
export async function replyPaneName(cfg, env, call) {
|
|
118
|
+
const ref = cfg.replyTo.trim();
|
|
119
|
+
if (!/^(terminal_)?\d+$/i.test(ref))
|
|
120
|
+
return undefined;
|
|
121
|
+
const args = { op: "list" };
|
|
122
|
+
if (cfg.replySession)
|
|
123
|
+
args.session = cfg.replySession;
|
|
124
|
+
const res = await call(args, localEnv(env)).catch(() => null);
|
|
125
|
+
if (!res?.ok)
|
|
126
|
+
return undefined;
|
|
127
|
+
const panes = (res.data.panes ?? [])
|
|
128
|
+
.filter((p) => !p.isPlugin);
|
|
129
|
+
const id = `terminal_${ref.replace(/^terminal_/i, "")}`;
|
|
130
|
+
const title = panes.find((p) => p.id === id)?.title?.trim();
|
|
131
|
+
if (!title || /^(Pane #\d+|-?(zsh|bash|fish|sh|pwsh|powershell))$/i.test(title))
|
|
132
|
+
return undefined;
|
|
133
|
+
const same = panes.filter((p) => (p.title ?? "").trim().toLowerCase() === title.toLowerCase());
|
|
134
|
+
return same.length === 1 ? title : undefined;
|
|
135
|
+
}
|
|
114
136
|
/** Paste `body` into the reply pane, retrying with backoff; false leaves it in undelivered/. */
|
|
115
137
|
export async function deliverMessage(cfg, body, dir, deps = {}) {
|
|
116
138
|
const env = deps.env ?? process.env;
|
|
@@ -125,13 +147,20 @@ export async function deliverMessage(cfg, body, dir, deps = {}) {
|
|
|
125
147
|
await sleep(delay * 1000);
|
|
126
148
|
// Generous Zellij budgets: this runs exactly when the machine is busiest.
|
|
127
149
|
const args = {
|
|
128
|
-
op: "send", to: cfg.replyTo, body, from: cfg.from, fresh: true, settleMs: 800, observeMs: 30_000,
|
|
150
|
+
op: "send", to: cfg.replyToName ?? cfg.replyTo, body, from: cfg.from, fresh: true, settleMs: 800, observeMs: 30_000,
|
|
151
|
+
timeoutMs: 60_000,
|
|
129
152
|
};
|
|
130
153
|
if (cfg.replySession)
|
|
131
154
|
args.session = cfg.replySession;
|
|
132
155
|
if (cfg.harness)
|
|
133
156
|
args.harness = cfg.harness;
|
|
134
|
-
const
|
|
157
|
+
const send = (a) => call(a, localEnv(env)).catch((err) => ({ ok: false, error: { code: "failed", message: String(err) } }));
|
|
158
|
+
let res = await send(args);
|
|
159
|
+
if (!res.ok && cfg.replyToName && (res.error.code === "peer_not_found" || res.error.code === "peer_ambiguous")) {
|
|
160
|
+
// The named pane is gone or now ambiguous: the id it had at start is the last resort.
|
|
161
|
+
log(`message attempt ${i + 1}: pane "${cfg.replyToName}" ${res.error.code}; trying ${cfg.replyTo}`);
|
|
162
|
+
res = await send({ ...args, to: cfg.replyTo });
|
|
163
|
+
}
|
|
135
164
|
if (res.ok) {
|
|
136
165
|
const submitted = String(res.data?.submitted ?? "unknown");
|
|
137
166
|
recordDelivery(dir, submitted, now());
|
|
@@ -430,6 +459,7 @@ export async function startRelay(input) {
|
|
|
430
459
|
};
|
|
431
460
|
// Read the body before the relay directory (and its token copy) exists.
|
|
432
461
|
const body = input.bodyFile ? readFileSync(input.bodyFile, "utf8") : undefined;
|
|
462
|
+
cfg.replyToName = await replyPaneName(cfg, env, input.deps?.call ?? ((args, e) => dispatchZswarm(args, undefined, { env: e })));
|
|
433
463
|
const dir = prepareRelay(cfg, { check: input.check, message: input.message }, env);
|
|
434
464
|
const store = input.store ?? createStateStore({ env });
|
|
435
465
|
const leaseTarget = { serve, session: cfg.session, pane: cfg.to };
|
package/dist/index.d.ts
CHANGED
|
@@ -5,11 +5,12 @@ export { createGitClient, defaultWorktreeRoot, normalizeRepoPath, parseWorktreeL
|
|
|
5
5
|
export { expandHomePath, looksLikeZswarmBinary, isZellijVersionOutput, resolveZellijBinary, resetZellijIdentityCache, sanitizeZellijEnv, validateSshDestination, validateSshMode, type ZellijExecFn, type ZellijExecResult, } from "./zellij/binary.js";
|
|
6
6
|
export { normalizePaneId, parsePaneList, resolvePane, type ZellijPane, } from "./zellij/panes.js";
|
|
7
7
|
export { liveSessionNames, parseCreatedAge, parseSessionList, resolveSelfPaneId, resolveSelfSession, type ZellijSession, type ZellijSessionResolve, } from "./zellij/session.js";
|
|
8
|
-
export {
|
|
8
|
+
export { assignServersToSessions, findAncestorServer, liveServers, matchServerToSession, paneMatchesId, parseServerArgs, readProcessTable, type ProcInfo, type ProcessRunner, type ReadProcessTableOptions, type ServerInfo, type SessionPanes, } from "./zellij/server-identity.js";
|
|
9
|
+
export { buildClosePaneArgs, buildDumpArgs, buildLaunchPluginArgs, buildListPanesArgs, buildNewPaneArgs, buildNewTabArgs, buildPasteArgs, buildPipeArgs, buildRenameSessionArgs, buildSendEnterArgs, buildSendKeysArgs, buildWriteCharsArgs, scrollbackPayload, waitPayload, type LaunchPluginInput, type NewPaneInput, type NewTabInput, type PaneDirection, type PipeInput, type ScrollbackRequest, type WaitRequest, } from "./zellij/args.js";
|
|
9
10
|
export { busPluginUrl, busToPanes, isBusPluginPane, nextConfigKey, parseBusReply, parseScrollbackReply, resolveBusPlugin, scrollbackToScreen, BUS_PIPE_NAME, DEFAULT_BUS_KEY, DEFAULT_BUS_TIMEOUT_MS, type BusMarker, type BusPane, type BusScrollback, type BusScrollbackPane, type BusSnapshot, } from "./zellij/bus.js";
|
|
10
11
|
export { busSnapshot, closeBusPluginPanes, planBus, resetBusCache, type BusPlan, } from "./ops/bus.js";
|
|
11
|
-
export { createZellijClient, type ZellijClient, type ZellijClientOptions, type ZellijTransport, } from "./zellij/client.js";
|
|
12
|
-
export { createStateStore, defaultStateDir, type LeaseRecord, type LogEntry, type SignalChannel, type StateStore, type StateStoreOptions, } from "./state.js";
|
|
12
|
+
export { createZellijClient, resetSelfResolutionCache, type ZellijClient, type ZellijClientOptions, type ZellijTransport, } from "./zellij/client.js";
|
|
13
|
+
export { createStateStore, defaultStateDir, type LeaseRecord, type LogEntry, type SessionAliasEntry, type SessionAliasMap, type SignalChannel, type StateStore, type StateStoreOptions, } from "./state.js";
|
|
13
14
|
export { findLease, leaseKey, leaseStale, listLeases, releaseLease, releaseLeaseKey, takeLease, type LeaseTakeInput, type LeaseTakeResult, type LeaseTarget, } from "./host/lease.js";
|
|
14
15
|
export { dispatchZswarm, resolveInvocationEnv } from "./ops/dispatch.js";
|
|
15
16
|
export { EXIT_WAIT_MS, HANDOFF_INLINE_MAX, HANDOFF_MARKER, HANDOFF_SELF_WAIT_MS, READY_SETTLE_MS, READY_STABLE_GAP_MS, READY_WAIT_MS, restartLaunchCommand, restartPane, restartPaneKind, type RestartPaneKind, type RestartStep, } from "./ops/restart.js";
|
|
@@ -38,6 +39,6 @@ export { findPython, runSlot, slotScriptPath } from "./host/slot.js";
|
|
|
38
39
|
export { installServeService, serveServiceName, serveServiceSpec, serveTokenFromEnv, serveTokenPath, uninstallServeService, SERVE_AWAIT_SESSION_ENV, SERVE_TOKEN_FILE_ENV, type ServeServiceInput, } from "./host/serve-service.js";
|
|
39
40
|
export { accountArgv, installServices, launchdLabel, launchdPlist, removeServices, serviceAccount, serviceStates, systemdUnitName, systemdUnits, writeAccountFile, type Runner, type ServiceAccount, type ServiceHost, type ServiceKind, type ServiceSpec, type ServiceState, } from "./host/services.js";
|
|
40
41
|
export { crewCheck, crewClient, crewDir, crewDown, crewServiceSpecs, crewStart, crewStatus, crewUp, firstLayoutPane, layoutPaneNames, parseEnvFile, type CrewDeps, type CrewUpInput, } from "./host/crew.js";
|
|
41
|
-
export { cancelAllTo, cancelRelay, deliverMessage, doneLines, fillMessage, freshDoneLine, launchRelay, listRelays, prepareRelay, pruneRelays, relayLoop, relaysDir, runRelayDir, scrubRelayToken, startRelay, waitForRelay, type RelayAdminDeps, type RelayCancelResult, type RelayConfig, type RelayDeps, type RelayJob, type RelayJobRunner, type RelayRow, type RelayStartInput, type RelayStatus, } from "./host/relay.js";
|
|
42
|
+
export { cancelAllTo, cancelRelay, deliverMessage, doneLines, fillMessage, freshDoneLine, launchRelay, listRelays, prepareRelay, pruneRelays, relayLoop, relaysDir, replyPaneName, runRelayDir, scrubRelayToken, startRelay, waitForRelay, type RelayAdminDeps, type RelayCancelResult, type RelayConfig, type RelayDeps, type RelayJob, type RelayJobRunner, type RelayRow, type RelayStartInput, type RelayStatus, } from "./host/relay.js";
|
|
42
43
|
export { hostChecks, hostDoctor, hostInstall, hostInstallArgs, hostScriptPath, parseHostReport, type HostCheck, type HostDoctorInput, type HostInstallInput, type SshRunner, } from "./host/host.js";
|
|
43
44
|
export { isSelfPane } from "./ops/guards.js";
|
package/dist/index.js
CHANGED
|
@@ -5,10 +5,11 @@ export { createGitClient, defaultWorktreeRoot, normalizeRepoPath, parseWorktreeL
|
|
|
5
5
|
export { expandHomePath, looksLikeZswarmBinary, isZellijVersionOutput, resolveZellijBinary, resetZellijIdentityCache, sanitizeZellijEnv, validateSshDestination, validateSshMode, } from "./zellij/binary.js";
|
|
6
6
|
export { normalizePaneId, parsePaneList, resolvePane, } from "./zellij/panes.js";
|
|
7
7
|
export { liveSessionNames, parseCreatedAge, parseSessionList, resolveSelfPaneId, resolveSelfSession, } from "./zellij/session.js";
|
|
8
|
-
export {
|
|
8
|
+
export { assignServersToSessions, findAncestorServer, liveServers, matchServerToSession, paneMatchesId, parseServerArgs, readProcessTable, } from "./zellij/server-identity.js";
|
|
9
|
+
export { buildClosePaneArgs, buildDumpArgs, buildLaunchPluginArgs, buildListPanesArgs, buildNewPaneArgs, buildNewTabArgs, buildPasteArgs, buildPipeArgs, buildRenameSessionArgs, buildSendEnterArgs, buildSendKeysArgs, buildWriteCharsArgs, scrollbackPayload, waitPayload, } from "./zellij/args.js";
|
|
9
10
|
export { busPluginUrl, busToPanes, isBusPluginPane, nextConfigKey, parseBusReply, parseScrollbackReply, resolveBusPlugin, scrollbackToScreen, BUS_PIPE_NAME, DEFAULT_BUS_KEY, DEFAULT_BUS_TIMEOUT_MS, } from "./zellij/bus.js";
|
|
10
11
|
export { busSnapshot, closeBusPluginPanes, planBus, resetBusCache, } from "./ops/bus.js";
|
|
11
|
-
export { createZellijClient, } from "./zellij/client.js";
|
|
12
|
+
export { createZellijClient, resetSelfResolutionCache, } from "./zellij/client.js";
|
|
12
13
|
export { createStateStore, defaultStateDir, } from "./state.js";
|
|
13
14
|
export { findLease, leaseKey, leaseStale, listLeases, releaseLease, releaseLeaseKey, takeLease, } from "./host/lease.js";
|
|
14
15
|
export { dispatchZswarm, resolveInvocationEnv } from "./ops/dispatch.js";
|
|
@@ -36,6 +37,6 @@ export { findPython, runSlot, slotScriptPath } from "./host/slot.js";
|
|
|
36
37
|
export { installServeService, serveServiceName, serveServiceSpec, serveTokenFromEnv, serveTokenPath, uninstallServeService, SERVE_AWAIT_SESSION_ENV, SERVE_TOKEN_FILE_ENV, } from "./host/serve-service.js";
|
|
37
38
|
export { accountArgv, installServices, launchdLabel, launchdPlist, removeServices, serviceAccount, serviceStates, systemdUnitName, systemdUnits, writeAccountFile, } from "./host/services.js";
|
|
38
39
|
export { crewCheck, crewClient, crewDir, crewDown, crewServiceSpecs, crewStart, crewStatus, crewUp, firstLayoutPane, layoutPaneNames, parseEnvFile, } from "./host/crew.js";
|
|
39
|
-
export { cancelAllTo, cancelRelay, deliverMessage, doneLines, fillMessage, freshDoneLine, launchRelay, listRelays, prepareRelay, pruneRelays, relayLoop, relaysDir, runRelayDir, scrubRelayToken, startRelay, waitForRelay, } from "./host/relay.js";
|
|
40
|
+
export { cancelAllTo, cancelRelay, deliverMessage, doneLines, fillMessage, freshDoneLine, launchRelay, listRelays, prepareRelay, pruneRelays, relayLoop, relaysDir, replyPaneName, runRelayDir, scrubRelayToken, startRelay, waitForRelay, } from "./host/relay.js";
|
|
40
41
|
export { hostChecks, hostDoctor, hostInstall, hostInstallArgs, hostScriptPath, parseHostReport, } from "./host/host.js";
|
|
41
42
|
export { isSelfPane } from "./ops/guards.js";
|
package/dist/ops/delivery.d.ts
CHANGED
|
@@ -26,8 +26,12 @@ export type SenderOrigin = {
|
|
|
26
26
|
* splittable), and the result is capped at 64 chars.
|
|
27
27
|
*/
|
|
28
28
|
export declare function sanitizeOriginPart(raw: unknown): string | null;
|
|
29
|
-
/**
|
|
30
|
-
|
|
29
|
+
/**
|
|
30
|
+
* The origin of the caller's own process, read from its environment.
|
|
31
|
+
* `session` overrides the env name when the caller has resolved the live
|
|
32
|
+
* session through the process table (a renamed session leaves the env stale).
|
|
33
|
+
*/
|
|
34
|
+
export declare function resolveSenderOrigin(env: NodeJS.ProcessEnv, session?: string | null): SenderOrigin;
|
|
31
35
|
/**
|
|
32
36
|
* The origin carried by a serve request. Only the serve worker trusts it: a
|
|
33
37
|
* local caller cannot claim another pane. Missing/garbage input (an older
|
package/dist/ops/delivery.js
CHANGED
|
@@ -20,8 +20,12 @@ export function sanitizeOriginPart(raw) {
|
|
|
20
20
|
? cleaned
|
|
21
21
|
: cleaned.slice(0, ORIGIN_PART_MAX).replace(/_+$/, "");
|
|
22
22
|
}
|
|
23
|
-
/**
|
|
24
|
-
|
|
23
|
+
/**
|
|
24
|
+
* The origin of the caller's own process, read from its environment.
|
|
25
|
+
* `session` overrides the env name when the caller has resolved the live
|
|
26
|
+
* session through the process table (a renamed session leaves the env stale).
|
|
27
|
+
*/
|
|
28
|
+
export function resolveSenderOrigin(env, session) {
|
|
25
29
|
const configured = env.ZSWARM_HOST;
|
|
26
30
|
const host = configured === undefined
|
|
27
31
|
? sanitizeOriginPart(hostname().split(".")[0])
|
|
@@ -30,7 +34,7 @@ export function resolveSenderOrigin(env) {
|
|
|
30
34
|
: sanitizeOriginPart(configured);
|
|
31
35
|
return {
|
|
32
36
|
host,
|
|
33
|
-
session: sanitizeOriginPart(resolveSelfSession(env)),
|
|
37
|
+
session: sanitizeOriginPart(session ?? resolveSelfSession(env)),
|
|
34
38
|
pane: sanitizeOriginPart(resolveSelfPaneId(env)),
|
|
35
39
|
via: null,
|
|
36
40
|
};
|
package/dist/ops/dispatch.js
CHANGED
|
@@ -216,6 +216,18 @@ async function unglueScreen(client, session, pane, text, full, fromBus) {
|
|
|
216
216
|
}
|
|
217
217
|
return unfoldScreen(ansi, columns) ?? text;
|
|
218
218
|
}
|
|
219
|
+
/**
|
|
220
|
+
* Ops whose sender header or self-pane guard needs the caller's live session.
|
|
221
|
+
* Read-only ops never pay for that resolution.
|
|
222
|
+
*/
|
|
223
|
+
const SELF_SESSION_OPS = new Set([
|
|
224
|
+
"send",
|
|
225
|
+
"broadcast",
|
|
226
|
+
"keys",
|
|
227
|
+
"interrupt",
|
|
228
|
+
"close",
|
|
229
|
+
"restart",
|
|
230
|
+
]);
|
|
219
231
|
async function dispatchOperation(args, injected, deps, context, env) {
|
|
220
232
|
const op = String(args.op ?? "");
|
|
221
233
|
const verbose = isVerbose(args);
|
|
@@ -322,10 +334,20 @@ async function dispatchOperation(args, injected, deps, context, env) {
|
|
|
322
334
|
manager: deps.serveTunnels,
|
|
323
335
|
});
|
|
324
336
|
}
|
|
325
|
-
const baseClient = injected ??
|
|
337
|
+
const baseClient = injected ??
|
|
338
|
+
createZellijClient({
|
|
339
|
+
env,
|
|
340
|
+
signal,
|
|
341
|
+
cache: isTrue(args.fresh) ? false : undefined,
|
|
342
|
+
// Rename memory so stale env names keep working across runs.
|
|
343
|
+
aliases: state(),
|
|
344
|
+
});
|
|
326
345
|
const client = {
|
|
327
346
|
...baseClient,
|
|
328
347
|
get transport() { return baseClient.transport; },
|
|
348
|
+
// Pass the getter through: a spread would freeze the env value and the
|
|
349
|
+
// self-pane guard would compare a stale name.
|
|
350
|
+
get selfSession() { return baseClient.selfSession; },
|
|
329
351
|
async resolveSession(...input) {
|
|
330
352
|
const resolved = await baseClient.resolveSession(...input);
|
|
331
353
|
context.session = resolved.session;
|
|
@@ -333,6 +355,15 @@ async function dispatchOperation(args, injected, deps, context, env) {
|
|
|
333
355
|
return resolved;
|
|
334
356
|
},
|
|
335
357
|
};
|
|
358
|
+
// Only ops that build a sender header or compare against the caller's own
|
|
359
|
+
// pane need the resolved self session; read-only ops must not pay a
|
|
360
|
+
// Zellij round-trip for it.
|
|
361
|
+
if (deps.viaServe !== true && SELF_SESSION_OPS.has(op) && typeof client.resolveSelf === "function") {
|
|
362
|
+
// The pane's ZELLIJ_SESSION_NAME can be stale after a rename; the header
|
|
363
|
+
// must name the live session peers can reply to.
|
|
364
|
+
const self = await client.resolveSelf();
|
|
365
|
+
args = { ...args, origin: resolveSenderOrigin(env, self) };
|
|
366
|
+
}
|
|
336
367
|
switch (op) {
|
|
337
368
|
case "sessions": {
|
|
338
369
|
// Resolve SSH/IPC first so a Windows interactive crew is reachable
|
package/dist/ops/panes.d.ts
CHANGED
|
@@ -3,7 +3,8 @@ import type { ZellijClient } from "../zellij/client.js";
|
|
|
3
3
|
import type { OpsResult } from "./types.js";
|
|
4
4
|
/**
|
|
5
5
|
* Give a pane (or its tab) a stable name, so peers can be addressed as
|
|
6
|
-
* `reviewer` instead of whatever id Zellij handed out.
|
|
6
|
+
* `reviewer` instead of whatever id Zellij handed out. With `sessionName`
|
|
7
|
+
* (and no `to`/`tab`) it renames the session itself instead.
|
|
7
8
|
*/
|
|
8
9
|
export declare function renameTarget(client: ZellijClient, args: Record<string, unknown>, policy?: Policy): Promise<OpsResult>;
|
|
9
10
|
export declare function focusTarget(client: ZellijClient, args: Record<string, unknown>, policy?: Policy): Promise<OpsResult>;
|
package/dist/ops/panes.js
CHANGED
|
@@ -7,14 +7,27 @@ async function session(client, args) {
|
|
|
7
7
|
}
|
|
8
8
|
/**
|
|
9
9
|
* Give a pane (or its tab) a stable name, so peers can be addressed as
|
|
10
|
-
* `reviewer` instead of whatever id Zellij handed out.
|
|
10
|
+
* `reviewer` instead of whatever id Zellij handed out. With `sessionName`
|
|
11
|
+
* (and no `to`/`tab`) it renames the session itself instead.
|
|
11
12
|
*/
|
|
12
13
|
export async function renameTarget(client, args, policy) {
|
|
14
|
+
const sessionName = optionalString(args.sessionName);
|
|
15
|
+
const tabKey = optionalString(args.tab);
|
|
16
|
+
const to = optionalString(args.to);
|
|
17
|
+
if (sessionName && !tabKey && !to) {
|
|
18
|
+
// Renaming the session itself: resolve the old name first so a stale
|
|
19
|
+
// ZELLIJ_SESSION_NAME or alias still names the live session.
|
|
20
|
+
const resolved = await client.resolveSession(typeof args.session === "string" ? args.session : undefined);
|
|
21
|
+
const renamed = await client.renameSession({
|
|
22
|
+
session: resolved.session,
|
|
23
|
+
name: sessionName,
|
|
24
|
+
});
|
|
25
|
+
return { ok: true, data: { session: renamed.session, was: renamed.was } };
|
|
26
|
+
}
|
|
13
27
|
const name = optionalString(args.name);
|
|
14
28
|
if (!name)
|
|
15
29
|
throw new ZellijError("missing_name", "name required");
|
|
16
30
|
const sess = await session(client, args);
|
|
17
|
-
const tabKey = optionalString(args.tab);
|
|
18
31
|
if (tabKey) {
|
|
19
32
|
const tab = client.resolveTab(await client.listTabs(sess, undefined, { fresh: true }), tabKey);
|
|
20
33
|
const renamed = await client.renameTab({
|
|
@@ -27,7 +40,6 @@ export async function renameTarget(client, args, policy) {
|
|
|
27
40
|
data: { session: sess, tab: renamed.tabId, was: tab.name, name },
|
|
28
41
|
};
|
|
29
42
|
}
|
|
30
|
-
const to = optionalString(args.to);
|
|
31
43
|
if (!to)
|
|
32
44
|
throw new ZellijError("missing_peer", "to (or tab) required");
|
|
33
45
|
const pane = client.resolvePane(await client.listPanes(sess, undefined, { fresh: true }), to);
|
package/dist/schema.js
CHANGED
|
@@ -446,6 +446,13 @@ export const PARAMS = [
|
|
|
446
446
|
ops: ["spawn", "rename"],
|
|
447
447
|
description: "spawn: pane (or tab) name; rename: the new name",
|
|
448
448
|
},
|
|
449
|
+
{
|
|
450
|
+
name: "sessionName",
|
|
451
|
+
type: "string",
|
|
452
|
+
flags: ["--session-name"],
|
|
453
|
+
ops: ["rename"],
|
|
454
|
+
description: "rename: rename the session itself to this name; the old name stays an alias so panes that still carry it keep working",
|
|
455
|
+
},
|
|
449
456
|
{
|
|
450
457
|
name: "submit",
|
|
451
458
|
type: "string",
|
package/dist/state.d.ts
CHANGED
|
@@ -55,6 +55,20 @@ export type LeaseRecord = {
|
|
|
55
55
|
/** Relays that forced through while this lease was held. */
|
|
56
56
|
sharedBy?: string[];
|
|
57
57
|
};
|
|
58
|
+
/**
|
|
59
|
+
* What an old session name now resolves to. Zellij never updates the
|
|
60
|
+
* ZELLIJ_SESSION_NAME a pane started with, so after a rename every older pane
|
|
61
|
+
* keeps the old name; this is how zswarm maps it to the live session. The
|
|
62
|
+
* server pid pins the entry to the process that owned the old name, so a
|
|
63
|
+
* later session reusing that name does not inherit the alias.
|
|
64
|
+
*/
|
|
65
|
+
export type SessionAliasEntry = {
|
|
66
|
+
current: string;
|
|
67
|
+
serverPid: number | null;
|
|
68
|
+
creation: string | null;
|
|
69
|
+
at: number;
|
|
70
|
+
};
|
|
71
|
+
export type SessionAliasMap = Record<string, SessionAliasEntry>;
|
|
58
72
|
/** How much of the end of the log `readLog` reads; older entries are not shown. */
|
|
59
73
|
export declare const LOG_TAIL_BYTES: number;
|
|
60
74
|
/**
|
|
@@ -78,6 +92,8 @@ export declare function createStateStore(options?: StateStoreOptions): {
|
|
|
78
92
|
readCursor: (key: string) => string | null;
|
|
79
93
|
writeCursor: (key: string, text: string) => void;
|
|
80
94
|
clearCursor: (key: string) => void;
|
|
95
|
+
readSessionAliases: () => SessionAliasMap;
|
|
96
|
+
recordSessionAlias: (oldName: string, entry: SessionAliasEntry) => void;
|
|
81
97
|
readBus: (session: string) => BusMarkerRecord | null;
|
|
82
98
|
writeBus: (session: string, marker: BusMarkerRecord) => void;
|
|
83
99
|
clearBus: (session?: string) => void;
|
package/dist/state.js
CHANGED
|
@@ -9,6 +9,8 @@ const CURSORS_LOCK = "cursors.lock";
|
|
|
9
9
|
const BUS_FILE = "bus.json";
|
|
10
10
|
const LEASES_FILE = "leases.json";
|
|
11
11
|
const LEASES_LOCK = "leases.lock";
|
|
12
|
+
const SESSION_ALIASES_FILE = "session-aliases.json";
|
|
13
|
+
const SESSION_ALIASES_LOCK = "session-aliases.lock";
|
|
12
14
|
/** How much of the end of the log `readLog` reads; older entries are not shown. */
|
|
13
15
|
export const LOG_TAIL_BYTES = 512 * 1024;
|
|
14
16
|
/**
|
|
@@ -397,6 +399,52 @@ export function createStateStore(options = {}) {
|
|
|
397
399
|
writeJson(CURSORS_FILE, all);
|
|
398
400
|
});
|
|
399
401
|
}
|
|
402
|
+
function asAliasEntry(value) {
|
|
403
|
+
if (!value || typeof value !== "object")
|
|
404
|
+
return null;
|
|
405
|
+
const rec = value;
|
|
406
|
+
if (typeof rec.current !== "string" || !rec.current)
|
|
407
|
+
return null;
|
|
408
|
+
return {
|
|
409
|
+
current: rec.current,
|
|
410
|
+
serverPid: typeof rec.serverPid === "number" &&
|
|
411
|
+
Number.isInteger(rec.serverPid) &&
|
|
412
|
+
rec.serverPid > 0
|
|
413
|
+
? rec.serverPid
|
|
414
|
+
: null,
|
|
415
|
+
creation: typeof rec.creation === "string" && rec.creation ? rec.creation : null,
|
|
416
|
+
at: typeof rec.at === "number" ? rec.at : 0,
|
|
417
|
+
};
|
|
418
|
+
}
|
|
419
|
+
function readSessionAliases() {
|
|
420
|
+
const raw = readJson(SESSION_ALIASES_FILE, {});
|
|
421
|
+
const out = {};
|
|
422
|
+
for (const [name, entry] of Object.entries(raw)) {
|
|
423
|
+
const parsed = asAliasEntry(entry);
|
|
424
|
+
if (parsed)
|
|
425
|
+
out[name] = parsed;
|
|
426
|
+
}
|
|
427
|
+
return out;
|
|
428
|
+
}
|
|
429
|
+
/**
|
|
430
|
+
* Remember `oldName -> entry.current` and repoint every existing entry whose
|
|
431
|
+
* current name was the old one, so rename chains A -> B -> C leave both A
|
|
432
|
+
* and B resolving to C.
|
|
433
|
+
*/
|
|
434
|
+
function recordSessionAlias(oldName, entry) {
|
|
435
|
+
const key = oldName.trim();
|
|
436
|
+
if (!key)
|
|
437
|
+
return;
|
|
438
|
+
withFileLock(SESSION_ALIASES_LOCK, () => {
|
|
439
|
+
const all = readSessionAliases();
|
|
440
|
+
for (const [name, previous] of Object.entries(all)) {
|
|
441
|
+
if (previous.current === key)
|
|
442
|
+
all[name] = { ...previous, current: entry.current };
|
|
443
|
+
}
|
|
444
|
+
all[key] = entry;
|
|
445
|
+
writeJson(SESSION_ALIASES_FILE, all);
|
|
446
|
+
});
|
|
447
|
+
}
|
|
400
448
|
function asMarker(value) {
|
|
401
449
|
if (!value || typeof value !== "object")
|
|
402
450
|
return null;
|
|
@@ -495,6 +543,8 @@ export function createStateStore(options = {}) {
|
|
|
495
543
|
readCursor,
|
|
496
544
|
writeCursor,
|
|
497
545
|
clearCursor,
|
|
546
|
+
readSessionAliases,
|
|
547
|
+
recordSessionAlias,
|
|
498
548
|
readBus,
|
|
499
549
|
writeBus,
|
|
500
550
|
clearBus,
|
package/dist/zellij/args.d.ts
CHANGED
|
@@ -88,6 +88,8 @@ export declare function buildSendKeysArgs(session: string, paneId: string, keys:
|
|
|
88
88
|
export declare function buildWriteCharsArgs(session: string, paneId: string, chars: string): string[];
|
|
89
89
|
export declare function buildRenamePaneArgs(session: string, paneId: string, name: string): string[];
|
|
90
90
|
export declare function buildRenameTabArgs(session: string, tabId: number, name: string): string[];
|
|
91
|
+
/** Rename the session itself: `zellij --session <old> action rename-session <new>`. */
|
|
92
|
+
export declare function buildRenameSessionArgs(session: string, name: string): string[];
|
|
91
93
|
export declare function buildFocusPaneArgs(session: string, paneId: string): string[];
|
|
92
94
|
export declare function buildListTabsArgs(session: string): string[];
|
|
93
95
|
export declare function buildDumpLayoutArgs(session: string): string[];
|
package/dist/zellij/args.js
CHANGED
|
@@ -183,6 +183,10 @@ export function buildRenameTabArgs(session, tabId, name) {
|
|
|
183
183
|
name,
|
|
184
184
|
];
|
|
185
185
|
}
|
|
186
|
+
/** Rename the session itself: `zellij --session <old> action rename-session <new>`. */
|
|
187
|
+
export function buildRenameSessionArgs(session, name) {
|
|
188
|
+
return [...sessionPrefix(session), "action", "rename-session", name];
|
|
189
|
+
}
|
|
186
190
|
export function buildFocusPaneArgs(session, paneId) {
|
|
187
191
|
return [
|
|
188
192
|
...sessionPrefix(session),
|
package/dist/zellij/client.d.ts
CHANGED
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
import { ListingCache } from "./cache.js";
|
|
2
|
-
import { buildClosePaneArgs, buildDumpArgs, buildListPanesArgs, buildNewPaneArgs, buildNewTabArgs, buildPasteArgs, buildSendEnterArgs, buildSendKeysArgs, buildWriteCharsArgs, type WaitRequest, type LaunchPluginInput, type NewPaneInput, type NewTabInput, type PipeInput } from "./args.js";
|
|
2
|
+
import { buildClosePaneArgs, buildDumpArgs, buildListPanesArgs, buildNewPaneArgs, buildNewTabArgs, buildPasteArgs, buildRenameSessionArgs, buildSendEnterArgs, buildSendKeysArgs, buildWriteCharsArgs, type WaitRequest, type LaunchPluginInput, type NewPaneInput, type NewTabInput, type PipeInput } from "./args.js";
|
|
3
3
|
import { resolveTab, type ZellijTab } from "./tabs.js";
|
|
4
4
|
import { type IpcDiscoveryState } from "../exec.js";
|
|
5
5
|
import { type SenderOrigin } from "../ops/delivery.js";
|
|
6
6
|
import { type ZellijExecFn } from "./binary.js";
|
|
7
7
|
import { normalizePaneId, resolvePane, type ZellijPane } from "./panes.js";
|
|
8
|
+
import { type ProcInfo } from "./server-identity.js";
|
|
8
9
|
import { type ZellijSession, type ZellijSessionResolve } from "./session.js";
|
|
10
|
+
import type { SessionAliasEntry, SessionAliasMap } from "../state.js";
|
|
9
11
|
export type ZellijClientOptions = {
|
|
10
12
|
exec?: ZellijExecFn;
|
|
11
13
|
zellijPath?: string;
|
|
@@ -18,7 +20,18 @@ export type ZellijClientOptions = {
|
|
|
18
20
|
cache?: ListingCache | false;
|
|
19
21
|
/** Clock for cache TTLs; process deadlines continue using wall time. */
|
|
20
22
|
now?: () => number;
|
|
23
|
+
/** Rename memory (StateStore); injected by dispatch, or by tests. */
|
|
24
|
+
aliases?: {
|
|
25
|
+
readSessionAliases(): SessionAliasMap;
|
|
26
|
+
recordSessionAlias(oldName: string, entry: SessionAliasEntry): void;
|
|
27
|
+
};
|
|
28
|
+
/** Process-table snapshot override (tests); defaults to a real read. */
|
|
29
|
+
processTable?: () => ProcInfo[] | Promise<ProcInfo[]>;
|
|
30
|
+
/** Platform override (tests); defaults to process.platform. */
|
|
31
|
+
platform?: NodeJS.Platform;
|
|
21
32
|
};
|
|
33
|
+
/** Test helper: forget every cached self resolution (and its process table). */
|
|
34
|
+
export declare function resetSelfResolutionCache(): void;
|
|
22
35
|
export type ZellijTransport = {
|
|
23
36
|
kind: "local" | "ssh";
|
|
24
37
|
mode: "local" | "ssh" | "interactive";
|
|
@@ -33,7 +46,12 @@ export type ZellijTransport = {
|
|
|
33
46
|
export declare function createZellijClient(options?: ZellijClientOptions): {
|
|
34
47
|
zellijPath: string;
|
|
35
48
|
selfPaneId: string | null;
|
|
36
|
-
|
|
49
|
+
/**
|
|
50
|
+
* The caller's session: the resolved live name once resolveSelf (or a
|
|
51
|
+
* self resolveSession) has run, else the raw ZELLIJ_SESSION_NAME. A getter
|
|
52
|
+
* so a rename resolution is visible to the self-pane guard.
|
|
53
|
+
*/
|
|
54
|
+
readonly selfSession: string | null;
|
|
37
55
|
contextKey: () => string;
|
|
38
56
|
invalidateListings: (session: string) => void;
|
|
39
57
|
observeManifest: (session: string, revision: string) => void;
|
|
@@ -45,6 +63,7 @@ export declare function createZellijClient(options?: ZellijClientOptions): {
|
|
|
45
63
|
}) => Promise<ZellijSession[]>;
|
|
46
64
|
deleteSession: (name: string, callTimeoutMs?: number) => Promise<void>;
|
|
47
65
|
resolveSession: (explicit?: string | null, callTimeoutMs?: number) => Promise<ZellijSessionResolve>;
|
|
66
|
+
resolveSelf: () => Promise<string | null>;
|
|
48
67
|
listPanes: (session: string, callTimeoutMs?: number, opts?: {
|
|
49
68
|
fresh?: boolean;
|
|
50
69
|
}) => Promise<ZellijPane[]>;
|
|
@@ -121,6 +140,14 @@ export declare function createZellijClient(options?: ZellijClientOptions): {
|
|
|
121
140
|
session: string;
|
|
122
141
|
name: string;
|
|
123
142
|
}>;
|
|
143
|
+
renameSession: (input: {
|
|
144
|
+
session: string;
|
|
145
|
+
name: string;
|
|
146
|
+
callTimeoutMs?: number;
|
|
147
|
+
}) => Promise<{
|
|
148
|
+
session: string;
|
|
149
|
+
was: string;
|
|
150
|
+
}>;
|
|
124
151
|
focusPane: (input: {
|
|
125
152
|
session: string;
|
|
126
153
|
paneId: string;
|
|
@@ -194,5 +221,6 @@ export declare function createZellijClient(options?: ZellijClientOptions): {
|
|
|
194
221
|
buildClosePaneArgs: typeof buildClosePaneArgs;
|
|
195
222
|
buildNewPaneArgs: typeof buildNewPaneArgs;
|
|
196
223
|
buildNewTabArgs: typeof buildNewTabArgs;
|
|
224
|
+
buildRenameSessionArgs: typeof buildRenameSessionArgs;
|
|
197
225
|
};
|
|
198
226
|
export type ZellijClient = ReturnType<typeof createZellijClient>;
|