@zswarm/core 0.3.0 → 0.4.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-windows.js +18 -3
- package/dist/host/crew.js +8 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/ops/delivery.d.ts +33 -0
- package/dist/ops/delivery.js +95 -2
- package/dist/ops/dispatch.js +8 -1
- package/dist/ops/types.d.ts +6 -0
- package/dist/schema.js +2 -2
- package/dist/state.d.ts +8 -0
- package/dist/zellij/client.d.ts +2 -1
- package/dist/zellij/client.js +3 -2
- package/package.json +1 -1
|
@@ -337,11 +337,26 @@ export function buildCrewClientScript(action, params) {
|
|
|
337
337
|
PS_CLIENT_PREAMBLE,
|
|
338
338
|
`$marker = '${marker}'`,
|
|
339
339
|
psClientPidLine(marker),
|
|
340
|
+
"$all = @(Get-CimInstance Win32_Process)",
|
|
340
341
|
"$closed = @()",
|
|
341
342
|
"foreach ($clientPid in $clientPids) {",
|
|
342
|
-
" # The window is the launcher's console, so
|
|
343
|
-
" #
|
|
344
|
-
"
|
|
343
|
+
" # The window is the launcher's console, so its tree goes with it: the",
|
|
344
|
+
" # attached `zellij` client shares that console. The Zellij server is in the",
|
|
345
|
+
" # tree too (the client that created the session spawned it) and must stay,",
|
|
346
|
+
" # or the session dies with its window: skip it and everything under it.",
|
|
347
|
+
" # A child is never older than its parent, so a reused parent PID never",
|
|
348
|
+
" # links an unrelated process.",
|
|
349
|
+
" $tree = New-Object System.Collections.Generic.List[object]",
|
|
350
|
+
" $queue = New-Object System.Collections.Generic.Queue[object]",
|
|
351
|
+
" $queue.Enqueue(($all | Where-Object { $_.ProcessId -eq $clientPid } | Select-Object -First 1))",
|
|
352
|
+
" while ($queue.Count -gt 0) {",
|
|
353
|
+
" $proc = $queue.Dequeue()",
|
|
354
|
+
" if (-not $proc) { continue }",
|
|
355
|
+
" if ($proc.Name -eq 'zellij.exe' -and $proc.CommandLine -match '(^|\\s)--server(\\s|=)') { continue }",
|
|
356
|
+
" $tree.Add($proc)",
|
|
357
|
+
" foreach ($child in @($all | Where-Object { $_.ParentProcessId -eq $proc.ProcessId -and $_.CreationDate -ge $proc.CreationDate })) { $queue.Enqueue($child) }",
|
|
358
|
+
" }",
|
|
359
|
+
" for ($i = $tree.Count - 1; $i -ge 0; $i--) { Stop-Process -Id $tree[$i].ProcessId -Force -ErrorAction SilentlyContinue }",
|
|
345
360
|
" $closed += $clientPid",
|
|
346
361
|
"}",
|
|
347
362
|
"Write-ZswarmJson @{ closed = @($closed); count = $closed.Count }",
|
package/dist/host/crew.js
CHANGED
|
@@ -212,6 +212,14 @@ export async function crewStart(session, deps = {}) {
|
|
|
212
212
|
// `zellij --layout ... attach --create`, which `crew client` starts. That
|
|
213
213
|
// window is what `crew start` reopens when it is gone.
|
|
214
214
|
const client = windowsClient(deps, env);
|
|
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
|
+
}
|
|
215
223
|
for (let attempt = 1; attempt <= 3; attempt++) {
|
|
216
224
|
zellij(["delete-session", session], env); // clears only a dead session of this name
|
|
217
225
|
await client.close(session); // a window left over from the previous attempt
|
package/dist/index.d.ts
CHANGED
|
@@ -24,6 +24,7 @@ export { buildServeTaskScript, DEFAULT_SERVE_INSTALL_TIMEOUT_MS, installServeLog
|
|
|
24
24
|
export { allocateLoopbackPort, assertSafeSshTunnelOpts, buildSshTunnelArgv, createServeTunnelManager, DEFAULT_SSH_PORT, DEFAULT_SSH_SERVE_PORT, describeServeTarget, formatSshServeTarget, forwardServe, isSshServeTarget, parseSshServeTarget, redactSshUserinfo, serveTunnelCacheKey, SSH_SERVE_REMOTE_HOST, SSH_TUNNEL_KEEPALIVE_COUNT, SSH_TUNNEL_KEEPALIVE_INTERVAL_S, stripControllerRouting, type AcquireServeTunnelOptions, type ParsedSshServeTarget, type ServeTunnelHandle, type ServeTunnelManager, type ServeTunnelManagerOptions, } from "./ops/serve-tunnel.js";
|
|
25
25
|
export { parseTabList, resolveTab, type ZellijTab } from "./zellij/tabs.js";
|
|
26
26
|
export { selectTargets } from "./ops/broadcast.js";
|
|
27
|
+
export { originFromServeRequest, originSuffix, resolveSenderOrigin, sanitizeOriginPart, type SenderOrigin, } from "./ops/delivery.js";
|
|
27
28
|
export { diffScreens, cursorKey } from "./ops/tail.js";
|
|
28
29
|
export { classify, lastLine, mapPool, peerStatus, DEFAULT_STATUS_TIMEOUT_MS, STATUS_DUMP_CONCURRENCY, type PeerState, } from "./ops/status.js";
|
|
29
30
|
export { normalizeScreen, unfoldScreen, truncateDumpText, DEFAULT_DUMP_MAX_CHARS, DEFAULT_WAIT_MAX_CHARS, } from "./ops/util.js";
|
package/dist/index.js
CHANGED
|
@@ -24,6 +24,7 @@ export { buildServeTaskScript, DEFAULT_SERVE_INSTALL_TIMEOUT_MS, installServeLog
|
|
|
24
24
|
export { allocateLoopbackPort, assertSafeSshTunnelOpts, buildSshTunnelArgv, createServeTunnelManager, DEFAULT_SSH_PORT, DEFAULT_SSH_SERVE_PORT, describeServeTarget, formatSshServeTarget, forwardServe, isSshServeTarget, parseSshServeTarget, redactSshUserinfo, serveTunnelCacheKey, SSH_SERVE_REMOTE_HOST, SSH_TUNNEL_KEEPALIVE_COUNT, SSH_TUNNEL_KEEPALIVE_INTERVAL_S, stripControllerRouting, } from "./ops/serve-tunnel.js";
|
|
25
25
|
export { parseTabList, resolveTab } from "./zellij/tabs.js";
|
|
26
26
|
export { selectTargets } from "./ops/broadcast.js";
|
|
27
|
+
export { originFromServeRequest, originSuffix, resolveSenderOrigin, sanitizeOriginPart, } from "./ops/delivery.js";
|
|
27
28
|
export { diffScreens, cursorKey } from "./ops/tail.js";
|
|
28
29
|
export { classify, lastLine, mapPool, peerStatus, DEFAULT_STATUS_TIMEOUT_MS, STATUS_DUMP_CONCURRENCY, } from "./ops/status.js";
|
|
29
30
|
export { normalizeScreen, unfoldScreen, truncateDumpText, DEFAULT_DUMP_MAX_CHARS, DEFAULT_WAIT_MAX_CHARS, } from "./ops/util.js";
|
package/dist/ops/delivery.d.ts
CHANGED
|
@@ -4,6 +4,37 @@ import type { ZellijClient } from "../zellij/client.js";
|
|
|
4
4
|
import type { ZellijPane } from "../zellij/panes.js";
|
|
5
5
|
import type { Clock } from "./types.js";
|
|
6
6
|
export type Submitted = boolean | "queued" | "unverified" | "not-delivered";
|
|
7
|
+
/**
|
|
8
|
+
* Where a message came from: the sending machine, the sender's Zellij session
|
|
9
|
+
* and pane, and whether this host received the op over `serve`. Every part is
|
|
10
|
+
* optional so an older caller (or a host with no Zellij env) still works.
|
|
11
|
+
*/
|
|
12
|
+
export type SenderOrigin = {
|
|
13
|
+
/** Sending machine. */
|
|
14
|
+
host: string | null;
|
|
15
|
+
/** Sender's Zellij session. */
|
|
16
|
+
session: string | null;
|
|
17
|
+
/** Sender's pane id, e.g. "terminal_3". */
|
|
18
|
+
pane: string | null;
|
|
19
|
+
/** "serve" only when this host received the op over serve. */
|
|
20
|
+
via: "serve" | null;
|
|
21
|
+
};
|
|
22
|
+
/**
|
|
23
|
+
* Clean one origin part for the `[zswarm from=…]` header: non-strings and
|
|
24
|
+
* blanks become null, header-breaking characters become spaces, runs of
|
|
25
|
+
* whitespace collapse, and the result is capped at 64 chars.
|
|
26
|
+
*/
|
|
27
|
+
export declare function sanitizeOriginPart(raw: unknown): string | null;
|
|
28
|
+
/** The origin of the caller's own process, read from its environment. */
|
|
29
|
+
export declare function resolveSenderOrigin(env: NodeJS.ProcessEnv): SenderOrigin;
|
|
30
|
+
/**
|
|
31
|
+
* The origin carried by a serve request. Only the serve worker trusts it: a
|
|
32
|
+
* local caller cannot claim another pane. Missing/garbage input (an older
|
|
33
|
+
* client) keeps every part null but still records that serve carried it.
|
|
34
|
+
*/
|
|
35
|
+
export declare function originFromServeRequest(raw: unknown): SenderOrigin;
|
|
36
|
+
/** " @ session/pane on host via serve" (or the parts that are known). */
|
|
37
|
+
export declare function originSuffix(origin: SenderOrigin | null | undefined): string;
|
|
7
38
|
export type DeliveryResult = {
|
|
8
39
|
to: string;
|
|
9
40
|
ok: boolean;
|
|
@@ -143,4 +174,6 @@ export declare function deliverTo(client: ZellijClient, state: StateStore, args:
|
|
|
143
174
|
firstCallMs?: number;
|
|
144
175
|
/** The observation timeout the scaled windows are capped by. */
|
|
145
176
|
timeoutMs?: number;
|
|
177
|
+
/** Sender's environment; direct callers (restart, relay) fall back to it. */
|
|
178
|
+
env?: NodeJS.ProcessEnv;
|
|
146
179
|
}): Promise<DeliveryResult>;
|
package/dist/ops/delivery.js
CHANGED
|
@@ -1,5 +1,80 @@
|
|
|
1
|
+
import { hostname } from "node:os";
|
|
1
2
|
import { busyMarker, queuedPrompt, resolveHarness } from "../harness.js";
|
|
3
|
+
import { resolveSelfPaneId, resolveSelfSession } from "../zellij/session.js";
|
|
2
4
|
import { isTrue, numberArg, optionalString } from "./util.js";
|
|
5
|
+
/** Cap for one origin part; keeps a spoofed value from wrapping the header. */
|
|
6
|
+
const ORIGIN_PART_MAX = 64;
|
|
7
|
+
/**
|
|
8
|
+
* Clean one origin part for the `[zswarm from=…]` header: non-strings and
|
|
9
|
+
* blanks become null, header-breaking characters become spaces, runs of
|
|
10
|
+
* whitespace collapse, and the result is capped at 64 chars.
|
|
11
|
+
*/
|
|
12
|
+
export function sanitizeOriginPart(raw) {
|
|
13
|
+
if (typeof raw !== "string")
|
|
14
|
+
return null;
|
|
15
|
+
const cleaned = raw.replace(/[\r\n[\]]+/g, " ").replace(/\s+/g, " ").trim();
|
|
16
|
+
if (!cleaned)
|
|
17
|
+
return null;
|
|
18
|
+
return cleaned.length <= ORIGIN_PART_MAX
|
|
19
|
+
? cleaned
|
|
20
|
+
: cleaned.slice(0, ORIGIN_PART_MAX).trimEnd();
|
|
21
|
+
}
|
|
22
|
+
/** The origin of the caller's own process, read from its environment. */
|
|
23
|
+
export function resolveSenderOrigin(env) {
|
|
24
|
+
const configured = env.ZSWARM_HOST;
|
|
25
|
+
const host = configured === undefined
|
|
26
|
+
? sanitizeOriginPart(hostname().split(".")[0])
|
|
27
|
+
: configured === "-"
|
|
28
|
+
? null
|
|
29
|
+
: sanitizeOriginPart(configured);
|
|
30
|
+
return {
|
|
31
|
+
host,
|
|
32
|
+
session: sanitizeOriginPart(resolveSelfSession(env)),
|
|
33
|
+
pane: sanitizeOriginPart(resolveSelfPaneId(env)),
|
|
34
|
+
via: null,
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* The origin carried by a serve request. Only the serve worker trusts it: a
|
|
39
|
+
* local caller cannot claim another pane. Missing/garbage input (an older
|
|
40
|
+
* client) keeps every part null but still records that serve carried it.
|
|
41
|
+
*/
|
|
42
|
+
export function originFromServeRequest(raw) {
|
|
43
|
+
const record = raw !== null && typeof raw === "object" && !Array.isArray(raw)
|
|
44
|
+
? raw
|
|
45
|
+
: {};
|
|
46
|
+
return {
|
|
47
|
+
host: sanitizeOriginPart(record.host),
|
|
48
|
+
session: sanitizeOriginPart(record.session),
|
|
49
|
+
pane: sanitizeOriginPart(record.pane),
|
|
50
|
+
via: "serve",
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
/** " @ session/pane on host via serve" (or the parts that are known). */
|
|
54
|
+
export function originSuffix(origin) {
|
|
55
|
+
if (!origin)
|
|
56
|
+
return "";
|
|
57
|
+
const { session, pane, host, via } = origin;
|
|
58
|
+
const loc = session && pane ? `${session}/${pane}` : (pane ?? session ?? null);
|
|
59
|
+
return ((loc ? " @ " + loc : "") +
|
|
60
|
+
(host ? " on " + host : "") +
|
|
61
|
+
(via === "serve" ? " via serve" : ""));
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* An args-supplied origin, cleaned again (a direct caller may skip dispatch),
|
|
65
|
+
* or null when it is absent/not an object.
|
|
66
|
+
*/
|
|
67
|
+
function originArg(raw) {
|
|
68
|
+
if (raw === null || typeof raw !== "object" || Array.isArray(raw))
|
|
69
|
+
return null;
|
|
70
|
+
const record = raw;
|
|
71
|
+
return {
|
|
72
|
+
host: sanitizeOriginPart(record.host),
|
|
73
|
+
session: sanitizeOriginPart(record.session),
|
|
74
|
+
pane: sanitizeOriginPart(record.pane),
|
|
75
|
+
via: record.via === "serve" ? "serve" : null,
|
|
76
|
+
};
|
|
77
|
+
}
|
|
3
78
|
/** Fixed send windows before adaptive scaling; explicit flags always win. */
|
|
4
79
|
export const SEND_BUDGETS = {
|
|
5
80
|
/** Pause before checking the paste landed. */
|
|
@@ -97,7 +172,9 @@ export function attachKnownSender(args, env) {
|
|
|
97
172
|
return { ...args, from: sender.label };
|
|
98
173
|
}
|
|
99
174
|
export function bodyText(client, args, body) {
|
|
100
|
-
return isTrue(args.raw)
|
|
175
|
+
return isTrue(args.raw)
|
|
176
|
+
? body
|
|
177
|
+
: client.formatPeerMessage(senderLabel(args), body, originArg(args.origin));
|
|
101
178
|
}
|
|
102
179
|
/**
|
|
103
180
|
* Caller `submit=` always wins. With none given, the pane's harness picks
|
|
@@ -292,6 +369,10 @@ export async function submitAfterEnter(client, input) {
|
|
|
292
369
|
export async function deliverTo(client, state, args, input) {
|
|
293
370
|
const { session, pane, body, op, at, clock, firstCallMs = 0, timeoutMs = 30_000 } = input;
|
|
294
371
|
const from = senderLabel(args);
|
|
372
|
+
// Dispatch stamps args.origin for the header; a direct caller (restart
|
|
373
|
+
// handoff, relay) has none and is described by its own environment.
|
|
374
|
+
const origin = originArg(args.origin) ?? resolveSenderOrigin(input.env ?? process.env);
|
|
375
|
+
const headerArgs = { ...args, origin };
|
|
295
376
|
const override = optionalString(args.harness);
|
|
296
377
|
const mode = resolveSubmitMode(args, pane);
|
|
297
378
|
const profile = resolveHarness(pane, override);
|
|
@@ -313,7 +394,7 @@ export async function deliverTo(client, state, args, input) {
|
|
|
313
394
|
const paste = () => client.injectPane({
|
|
314
395
|
session,
|
|
315
396
|
paneId: pane.id,
|
|
316
|
-
text: bodyText(client,
|
|
397
|
+
text: bodyText(client, headerArgs, body),
|
|
317
398
|
});
|
|
318
399
|
let delivered = await paste();
|
|
319
400
|
let submitted = await verifySubmit(client, {
|
|
@@ -371,6 +452,10 @@ export async function deliverTo(client, state, args, input) {
|
|
|
371
452
|
session,
|
|
372
453
|
to: delivered.paneId,
|
|
373
454
|
from,
|
|
455
|
+
fromHost: origin.host,
|
|
456
|
+
fromSession: origin.session,
|
|
457
|
+
fromPane: origin.pane,
|
|
458
|
+
via: origin.via,
|
|
374
459
|
bytes: body.length,
|
|
375
460
|
ok: false,
|
|
376
461
|
detail: "submitted=not-delivered",
|
|
@@ -392,6 +477,10 @@ export async function deliverTo(client, state, args, input) {
|
|
|
392
477
|
session,
|
|
393
478
|
to: delivered.paneId,
|
|
394
479
|
from,
|
|
480
|
+
fromHost: origin.host,
|
|
481
|
+
fromSession: origin.session,
|
|
482
|
+
fromPane: origin.pane,
|
|
483
|
+
via: origin.via,
|
|
395
484
|
bytes: body.length,
|
|
396
485
|
ok: true,
|
|
397
486
|
detail: `submitted=${submitted}`,
|
|
@@ -414,6 +503,10 @@ export async function deliverTo(client, state, args, input) {
|
|
|
414
503
|
session,
|
|
415
504
|
to: pane.id,
|
|
416
505
|
from,
|
|
506
|
+
fromHost: origin.host,
|
|
507
|
+
fromSession: origin.session,
|
|
508
|
+
fromPane: origin.pane,
|
|
509
|
+
via: origin.via,
|
|
417
510
|
bytes: body.length,
|
|
418
511
|
ok: false,
|
|
419
512
|
detail: `${code}: ${message}`,
|
package/dist/ops/dispatch.js
CHANGED
|
@@ -12,7 +12,7 @@ import { broadcast } from "./broadcast.js";
|
|
|
12
12
|
import { busChanged, busOp, busScreens, busSnapshot, busWait, planBus } from "./bus.js";
|
|
13
13
|
import { dumpLayoutOp, focusTarget, listTabsOp, renameTarget, stackTargets, } from "./panes.js";
|
|
14
14
|
import { peerCheckpoint, peerDiff } from "./review.js";
|
|
15
|
-
import { attachKnownSender, deliverTo, scaleBudgets, withSenderLabel, selfPaneTitle } from "./delivery.js";
|
|
15
|
+
import { attachKnownSender, deliverTo, originFromServeRequest, resolveSenderOrigin, scaleBudgets, withSenderLabel, selfPaneTitle, } from "./delivery.js";
|
|
16
16
|
import { assertNotPlugin, assertNotSelf, assertExpectedScreen, assertWritable, } from "./guards.js";
|
|
17
17
|
import { readDeliveryLog } from "./log.js";
|
|
18
18
|
import { awaitSignal, listSignals, postSignal } from "./signals.js";
|
|
@@ -754,6 +754,13 @@ async function pruneExitedSessions(client, listed, args) {
|
|
|
754
754
|
export async function dispatchZswarm(args, injected, deps = {}) {
|
|
755
755
|
try {
|
|
756
756
|
const env = resolveInvocationEnv(args, deps.env ?? process.env);
|
|
757
|
+
// A local caller can never claim another pane or `via serve`: its origin is
|
|
758
|
+
// always read from the environment it runs in. Only the serve worker trusts
|
|
759
|
+
// the request's origin, sanitized, and stamps it `via serve`.
|
|
760
|
+
args =
|
|
761
|
+
deps.viaServe === true
|
|
762
|
+
? { ...args, origin: originFromServeRequest(args.origin) }
|
|
763
|
+
: { ...args, origin: resolveSenderOrigin(env) };
|
|
757
764
|
const context = invocationContext(args, env, injected?.transport);
|
|
758
765
|
const result = await dispatchOperation(args, injected, deps, context, env);
|
|
759
766
|
if (context.transport === "serve" && result.context) {
|
package/dist/ops/types.d.ts
CHANGED
|
@@ -25,6 +25,12 @@ export type DispatchDeps = {
|
|
|
25
25
|
state?: StateStore;
|
|
26
26
|
policy?: Policy;
|
|
27
27
|
env?: NodeJS.ProcessEnv;
|
|
28
|
+
/**
|
|
29
|
+
* True when this dispatch is the local worker behind `zswarm serve`: the
|
|
30
|
+
* request's own origin is trusted and stamped `via serve`, never replaced by
|
|
31
|
+
* the worker host's environment.
|
|
32
|
+
*/
|
|
33
|
+
viaServe?: boolean;
|
|
28
34
|
/** MCP cancellation; aborted waits stop instead of running to timeout. */
|
|
29
35
|
signal?: AbortSignal;
|
|
30
36
|
/** Process-owned ssh:// LocalForward manager. CLI disposes; MCP reuses. */
|
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)",
|
|
284
|
+
description: "send: sender label in the [zswarm from=…] prefix (default: ZSWARM_FROM, else the sending pane's title, else swarm). zswarm appends the sender's session/pane, host (ZSWARM_HOST) and `via serve`",
|
|
285
285
|
},
|
|
286
286
|
{
|
|
287
287
|
name: "raw",
|
|
@@ -648,7 +648,7 @@ export function cliUsage() {
|
|
|
648
648
|
const value = param.type === "boolean" ? "" : param.type === "number" ? " N" : " VALUE";
|
|
649
649
|
lines.push(` ${param.flags.join(", ").padEnd(28)}${value.trim().padEnd(6)}${param.description}`);
|
|
650
650
|
}
|
|
651
|
-
lines.push("", "Guards: writes refuse zswarm's own pane (--allow-self) and exited panes (--force). --expect requires the screen to contain a substring first.", "Bus: `zswarm bus --install` once per Zellij session. `--force` closes orphan bus panes and reloads; do not use it as a retry.", "Remote: ZSWARM_SSH (+ ZSWARM_TMP=auto or ZSWARM_SSH_MODE=interactive on Windows). Or run `zswarm serve --listen` next to Zellij and set ZSWARM_SERVE / --serve (host:port, tcp://, or ssh://user@host?servePort=9419) plus ZSWARM_SERVE_TOKEN. Serve defaults to loopback and always requires a token; an explicit local Tailscale IP is allowed only after host verification. Private raw TCP Tailscale Serve keeps the backend on 127.0.0.1 behind `tailscale serve --tcp=…` (docs/tailscale.md). ssh:// does not start remote serve. Windows default recipe: `zswarm serve --install` (verified readiness).", "Hosts (CLI only, see docs/hosts.md): `zswarm slot`, `zswarm crew`, `zswarm relay`, `zswarm host`; `zswarm <command> --help` for each.", "Doctor: `zswarm doctor --session crew` inspects local, --ssh, and --serve routes without installs, pane changes, or plugin launch. See docs/doctor.md and docs/tailscale.md.", "Env: ZSWARM_BIN, ZSWARM_PATH, ZSWARM_SESSION, ZSWARM_SELF_PANE, ZSWARM_FROM, ZELLIJ_PANE_ID, ZELLIJ_SESSION_NAME, ZSWARM_BUS, ZSWARM_BUS_PLUGIN, ZSWARM_SSH, ZSWARM_SSH_BIN, ZSWARM_SSH_OPTS, ZSWARM_TMP, ZSWARM_SSH_MODE, ZSWARM_SERVE, ZSWARM_SERVE_TOKEN, ZSWARM_TAILSCALE_BIN, ZSWARM_CACHE_TTL_MS", "");
|
|
651
|
+
lines.push("", "Guards: writes refuse zswarm's own pane (--allow-self) and exited panes (--force). --expect requires the screen to contain a substring first.", "Bus: `zswarm bus --install` once per Zellij session. `--force` closes orphan bus panes and reloads; do not use it as a retry.", "Remote: ZSWARM_SSH (+ ZSWARM_TMP=auto or ZSWARM_SSH_MODE=interactive on Windows). Or run `zswarm serve --listen` next to Zellij and set ZSWARM_SERVE / --serve (host:port, tcp://, or ssh://user@host?servePort=9419) plus ZSWARM_SERVE_TOKEN. Serve defaults to loopback and always requires a token; an explicit local Tailscale IP is allowed only after host verification. Private raw TCP Tailscale Serve keeps the backend on 127.0.0.1 behind `tailscale serve --tcp=…` (docs/tailscale.md). ssh:// does not start remote serve. Windows default recipe: `zswarm serve --install` (verified readiness).", "Hosts (CLI only, see docs/hosts.md): `zswarm slot`, `zswarm crew`, `zswarm relay`, `zswarm host`; `zswarm <command> --help` for each.", "Doctor: `zswarm doctor --session crew` inspects local, --ssh, and --serve routes without installs, pane changes, or plugin launch. See docs/doctor.md and docs/tailscale.md.", "Env: ZSWARM_BIN, ZSWARM_PATH, ZSWARM_SESSION, ZSWARM_SELF_PANE, ZSWARM_FROM, ZSWARM_HOST, ZELLIJ_PANE_ID, ZELLIJ_SESSION_NAME, ZSWARM_BUS, ZSWARM_BUS_PLUGIN, ZSWARM_SSH, ZSWARM_SSH_BIN, ZSWARM_SSH_OPTS, ZSWARM_TMP, ZSWARM_SSH_MODE, ZSWARM_SERVE, ZSWARM_SERVE_TOKEN, ZSWARM_TAILSCALE_BIN, ZSWARM_CACHE_TTL_MS", "");
|
|
652
652
|
return lines.join("\n");
|
|
653
653
|
}
|
|
654
654
|
/**
|
package/dist/state.d.ts
CHANGED
|
@@ -9,6 +9,14 @@ export type LogEntry = {
|
|
|
9
9
|
session?: string | null;
|
|
10
10
|
to?: string | null;
|
|
11
11
|
from?: string | null;
|
|
12
|
+
/** Sender's machine when the send was logged. */
|
|
13
|
+
fromHost?: string | null;
|
|
14
|
+
/** Sender's Zellij session. */
|
|
15
|
+
fromSession?: string | null;
|
|
16
|
+
/** Sender's pane id, e.g. "terminal_3". */
|
|
17
|
+
fromPane?: string | null;
|
|
18
|
+
/** "serve" when this host received the op over serve. */
|
|
19
|
+
via?: "serve" | null;
|
|
12
20
|
bytes?: number;
|
|
13
21
|
ok: boolean;
|
|
14
22
|
detail?: string | null;
|
package/dist/zellij/client.d.ts
CHANGED
|
@@ -2,6 +2,7 @@ import { ListingCache } from "./cache.js";
|
|
|
2
2
|
import { buildClosePaneArgs, buildDumpArgs, buildListPanesArgs, buildNewPaneArgs, buildNewTabArgs, buildPasteArgs, 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
|
+
import { type SenderOrigin } from "../ops/delivery.js";
|
|
5
6
|
import { type ZellijExecFn } from "./binary.js";
|
|
6
7
|
import { normalizePaneId, resolvePane, type ZellijPane } from "./panes.js";
|
|
7
8
|
import { type ZellijSession, type ZellijSessionResolve } from "./session.js";
|
|
@@ -183,7 +184,7 @@ export declare function createZellijClient(options?: ZellijClientOptions): {
|
|
|
183
184
|
}>;
|
|
184
185
|
resolveTab: typeof resolveTab;
|
|
185
186
|
normalizePaneId: typeof normalizePaneId;
|
|
186
|
-
formatPeerMessage: (from: string, body: string) => string;
|
|
187
|
+
formatPeerMessage: (from: string, body: string, origin?: SenderOrigin | null) => string;
|
|
187
188
|
buildListPanesArgs: typeof buildListPanesArgs;
|
|
188
189
|
buildPasteArgs: typeof buildPasteArgs;
|
|
189
190
|
buildSendEnterArgs: typeof buildSendEnterArgs;
|
package/dist/zellij/client.js
CHANGED
|
@@ -4,6 +4,7 @@ import { buildClosePaneArgs, buildDumpArgs, buildDumpLayoutArgs, buildFocusPaneA
|
|
|
4
4
|
import { DEFAULT_BUS_TIMEOUT_MS, parseBusReply, parseChangedReply, parseScrollbackReply, parseWaitReply, } from "./bus.js";
|
|
5
5
|
import { parseTabList, resolveTab } from "./tabs.js";
|
|
6
6
|
import { createSshExec } from "../exec.js";
|
|
7
|
+
import { originSuffix } from "../ops/delivery.js";
|
|
7
8
|
import { DEFAULT_TIMEOUT_MS, NOT_FOUND_EXIT, defaultExec, ensureZellijProbes, identityCacheKey, resolveSshTarget, resolveZellijBinary, sanitizeZellijEnv, zellijExecDetails, zellijMissingError, } from "./binary.js";
|
|
8
9
|
import { normalizePaneId, parsePaneList, resolvePane, } from "./panes.js";
|
|
9
10
|
import { isZellijNoSessionsOutput, parseSessionList, resolveSelfPaneId, resolveSelfSession, sessionFromEnv, sessionFromList, } from "./session.js";
|
|
@@ -363,9 +364,9 @@ export function createZellijClient(options = {}) {
|
|
|
363
364
|
};
|
|
364
365
|
}
|
|
365
366
|
/** Visible prefix so peer CLIs can tell zSwarm injects from human prompts. */
|
|
366
|
-
function formatPeerMessage(from, body) {
|
|
367
|
+
function formatPeerMessage(from, body, origin) {
|
|
367
368
|
const sender = from.trim() || "swarm";
|
|
368
|
-
return `[zswarm from=${sender}]\n${body}`;
|
|
369
|
+
return `[zswarm from=${sender}${originSuffix(origin)}]\n${body}`;
|
|
369
370
|
}
|
|
370
371
|
return {
|
|
371
372
|
zellijPath,
|