@zswarm/core 0.1.5 → 0.1.6
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/README.md +4 -1
- package/dist/exec.d.ts +12 -1
- package/dist/exec.js +89 -12
- package/dist/harness.js +12 -7
- package/dist/index.d.ts +7 -6
- package/dist/index.js +4 -4
- package/dist/ops/bus.d.ts +6 -3
- package/dist/ops/bus.js +55 -22
- package/dist/ops/dispatch.d.ts +7 -1
- package/dist/ops/dispatch.js +193 -65
- package/dist/ops/guards.d.ts +2 -0
- package/dist/ops/guards.js +11 -1
- package/dist/ops/observation.d.ts +8 -0
- package/dist/ops/observation.js +26 -0
- package/dist/ops/routing.d.ts +13 -0
- package/dist/ops/routing.js +21 -0
- package/dist/ops/spawn.d.ts +3 -3
- package/dist/ops/spawn.js +130 -64
- package/dist/ops/status.d.ts +30 -5
- package/dist/ops/status.js +231 -91
- package/dist/ops/types.d.ts +5 -1
- package/dist/ops/util.d.ts +3 -0
- package/dist/ops/util.js +2 -1
- package/dist/schema.d.ts +2 -0
- package/dist/schema.js +45 -6
- package/dist/zellij/args.d.ts +2 -0
- package/dist/zellij/binary.d.ts +34 -0
- package/dist/zellij/binary.js +169 -8
- package/dist/zellij/bus.d.ts +2 -0
- package/dist/zellij/bus.js +2 -1
- package/dist/zellij/client.d.ts +22 -5
- package/dist/zellij/client.js +97 -32
- package/dist/zellij/panes.js +3 -0
- package/dist/zellij/session.d.ts +19 -3
- package/dist/zellij/session.js +39 -9
- package/package.json +3 -3
package/README.md
CHANGED
|
@@ -27,7 +27,10 @@ const result = await dispatchZswarm({ op: "list" });
|
|
|
27
27
|
```
|
|
28
28
|
|
|
29
29
|
`dispatchZswarm` builds a Zellij client unless you pass one. The result is
|
|
30
|
-
`{ ok, data }` or `{ ok: false, error }
|
|
30
|
+
`{ ok, data }` or `{ ok: false, error }`, with routing `context` once the
|
|
31
|
+
invocation's destination is known.
|
|
32
|
+
|
|
33
|
+
Spawn lifecycle, body files, guarded keys, status tabs and routing context: [crew operations](../../docs/crew-operations.md).
|
|
31
34
|
|
|
32
35
|
Ops, env, and the event bus: the [zswarm README](../../README.md).
|
|
33
36
|
|
package/dist/exec.d.ts
CHANGED
|
@@ -39,9 +39,20 @@ export declare function quoteRemoteArg(arg: string, shell: RemoteShell): string;
|
|
|
39
39
|
* The string `ssh host <this>` runs. Tests assert this rather than spawning ssh.
|
|
40
40
|
*/
|
|
41
41
|
export declare function buildSshRemoteCommand(target: SshTarget, args: string[], tmp: string | undefined, timeoutMs: number, socketDir?: string): string;
|
|
42
|
+
/** Outcome of `ZSWARM_TMP=auto` discovery for session diagnostics. */
|
|
43
|
+
export type IpcDiscoveryState = {
|
|
44
|
+
requested: string | null;
|
|
45
|
+
/** `none` = no tmp routing; `resolved`/`failed` only after an auto attempt. */
|
|
46
|
+
status: "none" | "skipped" | "resolved" | "failed" | "cancelled" | "expired";
|
|
47
|
+
tmp?: string;
|
|
48
|
+
socketDir?: string;
|
|
49
|
+
};
|
|
50
|
+
export type SshExecFn = ExecFn & {
|
|
51
|
+
ipcState: IpcDiscoveryState;
|
|
52
|
+
};
|
|
42
53
|
/**
|
|
43
54
|
* Run the remote zellij over ssh. The whole invocation is quoted into a single
|
|
44
55
|
* command string, because the remote login shell re-parses it.
|
|
45
56
|
*/
|
|
46
|
-
export declare function createSshExec(target: SshTarget, env: NodeJS.ProcessEnv):
|
|
57
|
+
export declare function createSshExec(target: SshTarget, env: NodeJS.ProcessEnv): SshExecFn;
|
|
47
58
|
export declare function createExec(binPath: string, env: NodeJS.ProcessEnv): ExecFn;
|
package/dist/exec.js
CHANGED
|
@@ -35,7 +35,7 @@ export function buildSshRemoteCommand(target, args, tmp, timeoutMs, socketDir) {
|
|
|
35
35
|
}
|
|
36
36
|
return withTmp;
|
|
37
37
|
}
|
|
38
|
-
async function discoverRemoteIpc(runner, target,
|
|
38
|
+
async function discoverRemoteIpc(runner, target, remaining, signal) {
|
|
39
39
|
const shell = inferRemoteShell({
|
|
40
40
|
explicit: target.remoteShell,
|
|
41
41
|
remoteBin: target.remoteBin,
|
|
@@ -47,8 +47,14 @@ async function discoverRemoteIpc(runner, target, timeoutMs) {
|
|
|
47
47
|
? [windowsDiscoverRemote(), unixDiscoverRemote()]
|
|
48
48
|
: [unixDiscoverRemote(), windowsDiscoverRemote()];
|
|
49
49
|
for (const probe of probes) {
|
|
50
|
+
if (signal?.aborted)
|
|
51
|
+
return undefined;
|
|
52
|
+
const left = remaining();
|
|
53
|
+
if (left <= 0)
|
|
54
|
+
return undefined;
|
|
50
55
|
const result = await runner([...target.options, target.host, probe], {
|
|
51
|
-
timeoutMs,
|
|
56
|
+
timeoutMs: left,
|
|
57
|
+
signal,
|
|
52
58
|
});
|
|
53
59
|
const dirs = pickIpcDirs(parseZellijServerPaths(result.stdout));
|
|
54
60
|
if (dirs)
|
|
@@ -63,25 +69,96 @@ async function discoverRemoteIpc(runner, target, timeoutMs) {
|
|
|
63
69
|
export function createSshExec(target, env) {
|
|
64
70
|
const runner = createExec(target.ssh, env);
|
|
65
71
|
let cached;
|
|
66
|
-
|
|
72
|
+
let autoAttempted = false;
|
|
73
|
+
const requestedTmp = target.tmp?.trim() || null;
|
|
74
|
+
const ipcState = {
|
|
75
|
+
requested: requestedTmp,
|
|
76
|
+
status: !requestedTmp
|
|
77
|
+
? "none"
|
|
78
|
+
: requestedTmp.toLowerCase() === "auto"
|
|
79
|
+
? "none"
|
|
80
|
+
: "skipped",
|
|
81
|
+
...(requestedTmp && requestedTmp.toLowerCase() !== "auto"
|
|
82
|
+
? { tmp: requestedTmp, socketDir: "" }
|
|
83
|
+
: {}),
|
|
84
|
+
};
|
|
85
|
+
async function resolveIpc(remaining, signal) {
|
|
67
86
|
const requested = target.tmp?.trim();
|
|
68
|
-
if (!requested)
|
|
87
|
+
if (!requested) {
|
|
88
|
+
ipcState.requested = null;
|
|
89
|
+
ipcState.status = "none";
|
|
69
90
|
return undefined;
|
|
91
|
+
}
|
|
92
|
+
ipcState.requested = requested;
|
|
70
93
|
if (requested.toLowerCase() !== "auto") {
|
|
94
|
+
ipcState.status = "skipped";
|
|
95
|
+
ipcState.tmp = requested;
|
|
96
|
+
ipcState.socketDir = "";
|
|
71
97
|
return { tmp: requested, socketDir: "" };
|
|
72
98
|
}
|
|
73
|
-
if (cached)
|
|
99
|
+
if (cached) {
|
|
100
|
+
ipcState.status = "resolved";
|
|
101
|
+
ipcState.tmp = cached.tmp;
|
|
102
|
+
ipcState.socketDir = cached.socketDir;
|
|
74
103
|
return cached;
|
|
75
|
-
|
|
76
|
-
|
|
104
|
+
}
|
|
105
|
+
// Do not re-probe after a failed/expired/cancelled attempt on this exec.
|
|
106
|
+
if (autoAttempted)
|
|
107
|
+
return undefined;
|
|
108
|
+
if (signal?.aborted) {
|
|
109
|
+
ipcState.status = "cancelled";
|
|
110
|
+
return undefined;
|
|
111
|
+
}
|
|
112
|
+
if (remaining() <= 0) {
|
|
113
|
+
ipcState.status = "expired";
|
|
114
|
+
return undefined;
|
|
115
|
+
}
|
|
116
|
+
autoAttempted = true;
|
|
117
|
+
const dirs = await discoverRemoteIpc(runner, target, remaining, signal);
|
|
118
|
+
if (signal?.aborted) {
|
|
119
|
+
ipcState.status = "cancelled";
|
|
120
|
+
return undefined;
|
|
121
|
+
}
|
|
122
|
+
if (dirs) {
|
|
77
123
|
cached = dirs;
|
|
78
|
-
|
|
124
|
+
ipcState.status = "resolved";
|
|
125
|
+
ipcState.tmp = dirs.tmp;
|
|
126
|
+
ipcState.socketDir = dirs.socketDir;
|
|
127
|
+
return dirs;
|
|
128
|
+
}
|
|
129
|
+
ipcState.status = remaining() <= 0 ? "expired" : "failed";
|
|
130
|
+
return undefined;
|
|
79
131
|
}
|
|
80
|
-
|
|
81
|
-
const
|
|
82
|
-
const
|
|
83
|
-
|
|
132
|
+
const exec = async (args, options) => {
|
|
133
|
+
const deadline = Date.now() + options.timeoutMs;
|
|
134
|
+
const remaining = () => Math.max(0, deadline - Date.now());
|
|
135
|
+
const ipc = await resolveIpc(remaining, options.signal);
|
|
136
|
+
if (options.signal?.aborted) {
|
|
137
|
+
return {
|
|
138
|
+
code: -1,
|
|
139
|
+
stdout: "",
|
|
140
|
+
stderr: `${target.ssh} cancelled`,
|
|
141
|
+
};
|
|
142
|
+
}
|
|
143
|
+
const left = remaining();
|
|
144
|
+
// Do not launch the target command after the caller's budget is gone —
|
|
145
|
+
// especially after two discovery probes ate the whole timeout.
|
|
146
|
+
if (left <= 0) {
|
|
147
|
+
return {
|
|
148
|
+
code: -1,
|
|
149
|
+
stdout: "",
|
|
150
|
+
stderr: `${target.ssh} timed out after ${options.timeoutMs}ms`,
|
|
151
|
+
};
|
|
152
|
+
}
|
|
153
|
+
// auto discovery failed: still run, but callers read ipcState for reachability.
|
|
154
|
+
const remote = buildSshRemoteCommand(target, args, ipc?.tmp, left, ipc?.socketDir);
|
|
155
|
+
return runner([...target.options, target.host, remote], {
|
|
156
|
+
...options,
|
|
157
|
+
timeoutMs: left,
|
|
158
|
+
});
|
|
84
159
|
};
|
|
160
|
+
exec.ipcState = ipcState;
|
|
161
|
+
return exec;
|
|
85
162
|
}
|
|
86
163
|
/** Read stdout until the answer is in hand, then stop caring about the child. */
|
|
87
164
|
function runUntil(binPath, args, env, options) {
|
package/dist/harness.js
CHANGED
|
@@ -29,15 +29,20 @@ const GENERIC_WAITING = [
|
|
|
29
29
|
/press enter to continue/i,
|
|
30
30
|
];
|
|
31
31
|
/**
|
|
32
|
-
*
|
|
33
|
-
*
|
|
34
|
-
* show only the generic shapes so far, and unknown panes get the generic set
|
|
35
|
-
* and nothing else. Mislabelling a working pane as waiting makes a dispatcher
|
|
36
|
-
* type into it, so unobserved prompts stay undetected.
|
|
32
|
+
* Named approval questions complement structural menu detection in status.
|
|
33
|
+
* Keep patterns anchored: quoting a prompt in ordinary output is not a menu.
|
|
37
34
|
*/
|
|
38
35
|
const WAITING = {
|
|
39
|
-
codex:
|
|
40
|
-
|
|
36
|
+
codex: [
|
|
37
|
+
...GENERIC_WAITING,
|
|
38
|
+
/^(?:[›❯>]\s*)?Would you like to run the following command\?/i,
|
|
39
|
+
/^(?:[›❯>]\s*)?Do you want to (?:run|allow|approve) (?:this|the) (?:command|operation|action)\?/i,
|
|
40
|
+
],
|
|
41
|
+
cursor: [
|
|
42
|
+
...GENERIC_WAITING,
|
|
43
|
+
/^(?:[›❯>]\s*)?(?:Run this command|Allow this command|Do you want to run this command)\?/i,
|
|
44
|
+
/^(?:[›❯>]\s*)?(?:Allow|Approve) (?:this|the) (?:tool|command|operation)(?: call)?\?/i,
|
|
45
|
+
],
|
|
41
46
|
opencode: [...GENERIC_WAITING, /Permission required/i, /Allow once/i],
|
|
42
47
|
gemini: [
|
|
43
48
|
...GENERIC_WAITING,
|
package/dist/index.d.ts
CHANGED
|
@@ -1,25 +1,26 @@
|
|
|
1
1
|
export { ZellijError } from "./errors.js";
|
|
2
2
|
export { createExec, NOT_FOUND_EXIT, type ExecFn, type ExecOptions, type ExecResult, } from "./exec.js";
|
|
3
3
|
export { createGitClient, defaultWorktreeRoot, normalizeRepoPath, parseWorktreeList, worktreeDirName, type GitClient, type GitClientOptions, type Worktree, } from "./git.js";
|
|
4
|
-
export { expandHomePath, resolveZellijBinary, sanitizeZellijEnv, type ZellijExecFn, type ZellijExecResult, } from "./zellij/binary.js";
|
|
4
|
+
export { expandHomePath, looksLikeZswarmBinary, isZellijVersionOutput, resolveZellijBinary, resetZellijIdentityCache, sanitizeZellijEnv, validateSshDestination, validateSshMode, type ZellijExecFn, type ZellijExecResult, } from "./zellij/binary.js";
|
|
5
5
|
export { normalizePaneId, parsePaneList, resolvePane, type ZellijPane, } from "./zellij/panes.js";
|
|
6
|
-
export { parseSessionList, resolveSelfPaneId, type ZellijSessionResolve, } from "./zellij/session.js";
|
|
6
|
+
export { liveSessionNames, parseSessionList, resolveSelfPaneId, type ZellijSession, type ZellijSessionResolve, } from "./zellij/session.js";
|
|
7
7
|
export { buildClosePaneArgs, buildDumpArgs, buildLaunchPluginArgs, buildListPanesArgs, buildNewPaneArgs, buildNewTabArgs, buildPasteArgs, buildPipeArgs, buildSendEnterArgs, buildSendKeysArgs, buildWriteCharsArgs, scrollbackPayload, waitPayload, type LaunchPluginInput, type NewPaneInput, type NewTabInput, type PaneDirection, type PipeInput, type ScrollbackRequest, type WaitRequest, } from "./zellij/args.js";
|
|
8
8
|
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";
|
|
9
9
|
export { busSnapshot, closeBusPluginPanes, planBus, resetBusCache, type BusPlan, } from "./ops/bus.js";
|
|
10
|
-
export { createZellijClient, type ZellijClient, type ZellijClientOptions, } from "./zellij/client.js";
|
|
10
|
+
export { createZellijClient, type ZellijClient, type ZellijClientOptions, type ZellijTransport, } from "./zellij/client.js";
|
|
11
11
|
export { createStateStore, defaultStateDir, type LogEntry, type SignalChannel, type StateStore, type StateStoreOptions, } from "./state.js";
|
|
12
|
-
export { dispatchZswarm } from "./ops/dispatch.js";
|
|
12
|
+
export { dispatchZswarm, resolveInvocationEnv } from "./ops/dispatch.js";
|
|
13
13
|
export { assertOpAllowed, assertPaneAllowed, isWriteOp, loadPolicy, type Policy, } from "./policy.js";
|
|
14
|
-
export { buildSshRemoteCommand, createSshExec, quoteRemoteArg, shellQuote, type SshTarget, } from "./exec.js";
|
|
14
|
+
export { buildSshRemoteCommand, createSshExec, quoteRemoteArg, shellQuote, type IpcDiscoveryState, type SshExecFn, type SshTarget, } from "./exec.js";
|
|
15
15
|
export { resolveSshTarget } from "./zellij/binary.js";
|
|
16
16
|
export { applyIpcTmpEnv, cmdQuote, concreteTmp, encodePowerShellCommand, inferRemoteShell, parseZellijServerPaths, pickIpcDirs, pickIpcTmp, socketDirFromServerPath, tmpFromServerPath, unixDiscoverRemote, windowsDiscoverRemote, windowsInteractiveRemote, wrapWithTmpEnv, } from "./zellij/ipc.js";
|
|
17
17
|
export { callServe, DEFAULT_SERVE_LISTEN, installServeLogon, isLoopbackHost, parseListenAddress, redactServeSecret, SERVE_CALL_TIMEOUT_CAP_MS, SERVE_MAX_REQUEST_BYTES, SERVE_TASK_NAME, serveCallTimeout, serveChildEnv, serveLogonCommand, startServe, uninstallServeLogon, } from "./ops/serve.js";
|
|
18
18
|
export { parseTabList, resolveTab, type ZellijTab } from "./zellij/tabs.js";
|
|
19
19
|
export { selectTargets } from "./ops/broadcast.js";
|
|
20
20
|
export { diffScreens, cursorKey } from "./ops/tail.js";
|
|
21
|
-
export { classify, lastLine, type PeerState } from "./ops/status.js";
|
|
21
|
+
export { classify, lastLine, mapPool, peerStatus, DEFAULT_STATUS_TIMEOUT_MS, STATUS_DUMP_CONCURRENCY, type PeerState, } from "./ops/status.js";
|
|
22
22
|
export { normalizeScreen, truncateDumpText, DEFAULT_DUMP_MAX_CHARS, DEFAULT_WAIT_MAX_CHARS, } from "./ops/util.js";
|
|
23
|
+
export type { RoutingContext } from "./ops/routing.js";
|
|
23
24
|
export type { DispatchDeps, OpsResult } from "./ops/types.js";
|
|
24
25
|
export { normalizeKey, normalizeKeys, tokenizeCommand } from "./keys.js";
|
|
25
26
|
export { cliUsage, mcpInputSchema, parseCliArgv, MCP_TOOL_DESCRIPTION, OP_NAMES, PARAMS, TARGET_OPS, type OpName, type ParamSpec, type ParamType, } from "./schema.js";
|
package/dist/index.js
CHANGED
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
export { ZellijError } from "./errors.js";
|
|
2
2
|
export { createExec, NOT_FOUND_EXIT, } from "./exec.js";
|
|
3
3
|
export { createGitClient, defaultWorktreeRoot, normalizeRepoPath, parseWorktreeList, worktreeDirName, } from "./git.js";
|
|
4
|
-
export { expandHomePath, resolveZellijBinary, sanitizeZellijEnv, } from "./zellij/binary.js";
|
|
4
|
+
export { expandHomePath, looksLikeZswarmBinary, isZellijVersionOutput, resolveZellijBinary, resetZellijIdentityCache, sanitizeZellijEnv, validateSshDestination, validateSshMode, } from "./zellij/binary.js";
|
|
5
5
|
export { normalizePaneId, parsePaneList, resolvePane, } from "./zellij/panes.js";
|
|
6
|
-
export { parseSessionList, resolveSelfPaneId, } from "./zellij/session.js";
|
|
6
|
+
export { liveSessionNames, parseSessionList, resolveSelfPaneId, } from "./zellij/session.js";
|
|
7
7
|
export { buildClosePaneArgs, buildDumpArgs, buildLaunchPluginArgs, buildListPanesArgs, buildNewPaneArgs, buildNewTabArgs, buildPasteArgs, buildPipeArgs, buildSendEnterArgs, buildSendKeysArgs, buildWriteCharsArgs, scrollbackPayload, waitPayload, } from "./zellij/args.js";
|
|
8
8
|
export { busPluginUrl, busToPanes, isBusPluginPane, nextConfigKey, parseBusReply, parseScrollbackReply, resolveBusPlugin, scrollbackToScreen, BUS_PIPE_NAME, DEFAULT_BUS_KEY, DEFAULT_BUS_TIMEOUT_MS, } from "./zellij/bus.js";
|
|
9
9
|
export { busSnapshot, closeBusPluginPanes, planBus, resetBusCache, } from "./ops/bus.js";
|
|
10
10
|
export { createZellijClient, } from "./zellij/client.js";
|
|
11
11
|
export { createStateStore, defaultStateDir, } from "./state.js";
|
|
12
|
-
export { dispatchZswarm } from "./ops/dispatch.js";
|
|
12
|
+
export { dispatchZswarm, resolveInvocationEnv } from "./ops/dispatch.js";
|
|
13
13
|
export { assertOpAllowed, assertPaneAllowed, isWriteOp, loadPolicy, } from "./policy.js";
|
|
14
14
|
export { buildSshRemoteCommand, createSshExec, quoteRemoteArg, shellQuote, } from "./exec.js";
|
|
15
15
|
export { resolveSshTarget } from "./zellij/binary.js";
|
|
@@ -18,7 +18,7 @@ export { callServe, DEFAULT_SERVE_LISTEN, installServeLogon, isLoopbackHost, par
|
|
|
18
18
|
export { parseTabList, resolveTab } from "./zellij/tabs.js";
|
|
19
19
|
export { selectTargets } from "./ops/broadcast.js";
|
|
20
20
|
export { diffScreens, cursorKey } from "./ops/tail.js";
|
|
21
|
-
export { classify, lastLine } from "./ops/status.js";
|
|
21
|
+
export { classify, lastLine, mapPool, peerStatus, DEFAULT_STATUS_TIMEOUT_MS, STATUS_DUMP_CONCURRENCY, } from "./ops/status.js";
|
|
22
22
|
export { normalizeScreen, truncateDumpText, DEFAULT_DUMP_MAX_CHARS, DEFAULT_WAIT_MAX_CHARS, } from "./ops/util.js";
|
|
23
23
|
export { normalizeKey, normalizeKeys, tokenizeCommand } from "./keys.js";
|
|
24
24
|
export { cliUsage, mcpInputSchema, parseCliArgv, MCP_TOOL_DESCRIPTION, OP_NAMES, PARAMS, TARGET_OPS, } from "./schema.js";
|
package/dist/ops/bus.d.ts
CHANGED
|
@@ -26,7 +26,10 @@ export declare function planBus(client: ZellijClient, state: StateStore, env?: N
|
|
|
26
26
|
* --plugin` loads a new copy for each key — that is how a dead bus became
|
|
27
27
|
* dozens of WASM panes. One remembered key, then polling.
|
|
28
28
|
*/
|
|
29
|
-
export declare function busSnapshot(client: ZellijClient, state: StateStore, session: string, clock: Clock, env?: NodeJS.ProcessEnv, payload?: string
|
|
29
|
+
export declare function busSnapshot(client: ZellijClient, state: StateStore, session: string, clock: Clock, env?: NodeJS.ProcessEnv, payload?: string, opts?: {
|
|
30
|
+
deadlineAt?: number;
|
|
31
|
+
signal?: AbortSignal;
|
|
32
|
+
}): Promise<{
|
|
30
33
|
snapshot: BusSnapshot;
|
|
31
34
|
configKey: string;
|
|
32
35
|
} | null>;
|
|
@@ -41,7 +44,7 @@ export declare function busSnapshot(client: ZellijClient, state: StateStore, ses
|
|
|
41
44
|
* `dump-screen` returns them ragged. `normalizeScreen` erases exactly that
|
|
42
45
|
* difference, and every caller here already normalizes before comparing.
|
|
43
46
|
*/
|
|
44
|
-
export declare function busScreens(client: ZellijClient, state: StateStore, session: string, paneIds: string[], clock: Clock, env?: NodeJS.ProcessEnv): Promise<Map<string, string> | null>;
|
|
47
|
+
export declare function busScreens(client: ZellijClient, state: StateStore, session: string, paneIds: string[], clock: Clock, env?: NodeJS.ProcessEnv, timeoutMs?: number): Promise<Map<string, string> | null>;
|
|
45
48
|
/**
|
|
46
49
|
* Hand the whole wait to the plugin, which holds the pipe until it resolves.
|
|
47
50
|
* One process covers a minute-long wait instead of one per poll, and the plugin
|
|
@@ -56,7 +59,7 @@ export declare function busWait(client: ZellijClient, state: StateStore, session
|
|
|
56
59
|
* `status` answers by sampling twice, without the gap between samples — but
|
|
57
60
|
* relative to the previous call rather than a fixed window.
|
|
58
61
|
*/
|
|
59
|
-
export declare function busChanged(client: ZellijClient, state: StateStore, session: string, paneIds: string[], env?: NodeJS.ProcessEnv): Promise<BusChanged | null>;
|
|
62
|
+
export declare function busChanged(client: ZellijClient, state: StateStore, session: string, paneIds: string[], env?: NodeJS.ProcessEnv, timeoutMs?: number): Promise<BusChanged | null>;
|
|
60
63
|
/** Close every zswarm-bus plugin pane in this session. Best-effort per pane. */
|
|
61
64
|
export declare function closeBusPluginPanes(client: ZellijClient, session: string, pluginPath: string | null): Promise<string[]>;
|
|
62
65
|
/** Report on the bus, install it, or forget it. */
|
package/dist/ops/bus.js
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import { existsSync } from "node:fs";
|
|
2
2
|
import { ZellijError } from "../errors.js";
|
|
3
3
|
import { loadPolicy } from "../policy.js";
|
|
4
|
-
import { DEFAULT_BUS_KEY, busPluginUrl, isBusPluginPane, parseBusReply, parseChangedReply, parseScrollbackReply, parseWaitReply, resolveBusPlugin, scrollbackToScreen, } from "../zellij/bus.js";
|
|
5
|
-
import {
|
|
4
|
+
import { DEFAULT_BUS_KEY, DEFAULT_BUS_TIMEOUT_MS, busPluginUrl, isBusPluginPane, parseBusReply, parseChangedReply, parseScrollbackReply, parseWaitReply, resolveBusPlugin, scrollbackToScreen, } from "../zellij/bus.js";
|
|
5
|
+
import { DEFAULT_TIMEOUT_MS } from "../zellij/binary.js";
|
|
6
|
+
import { isTrue, throwIfAborted } from "./util.js";
|
|
6
7
|
/**
|
|
7
8
|
* Whether to ask the event-bus plugin, and what to do when it does not answer.
|
|
8
9
|
*
|
|
@@ -70,8 +71,10 @@ export function planBus(client, state, env = process.env, session = "") {
|
|
|
70
71
|
return { ...base, enabled: true, reason: "installed" };
|
|
71
72
|
return { ...base, reason: 'not installed; run zswarm({op:"bus", install:true})' };
|
|
72
73
|
}
|
|
73
|
-
async function askOnce(client, session, url, configKey, payload) {
|
|
74
|
-
const result = await client.pipePlugin({
|
|
74
|
+
async function askOnce(client, session, url, configKey, payload, timeoutMs) {
|
|
75
|
+
const result = await client.pipePlugin({
|
|
76
|
+
session, url, configKey, payload, timeoutMs,
|
|
77
|
+
});
|
|
75
78
|
return parseBusReply(result.stdout);
|
|
76
79
|
}
|
|
77
80
|
/**
|
|
@@ -80,14 +83,25 @@ async function askOnce(client, session, url, configKey, payload) {
|
|
|
80
83
|
* blind (ready:false, paneUpdates:0) until something moves. Renaming a pane
|
|
81
84
|
* to the title it already has is an invisible change that forces the push.
|
|
82
85
|
*/
|
|
83
|
-
async function nudgeManifest(client, session, env) {
|
|
86
|
+
async function nudgeManifest(client, session, env, remaining) {
|
|
84
87
|
if (loadPolicy(env).readOnly)
|
|
85
88
|
return;
|
|
86
|
-
const
|
|
89
|
+
const listBudget = Math.min(DEFAULT_TIMEOUT_MS, remaining());
|
|
90
|
+
if (listBudget <= 0)
|
|
91
|
+
return;
|
|
92
|
+
const panes = await client.listPanes(session, listBudget);
|
|
87
93
|
const pane = panes.find((p) => !p.isPlugin && !p.exited && p.title.trim());
|
|
88
94
|
if (!pane)
|
|
89
95
|
return;
|
|
90
|
-
|
|
96
|
+
const renameBudget = Math.min(DEFAULT_TIMEOUT_MS, remaining());
|
|
97
|
+
if (renameBudget <= 0)
|
|
98
|
+
return;
|
|
99
|
+
await client.renamePane({
|
|
100
|
+
session,
|
|
101
|
+
paneId: pane.id,
|
|
102
|
+
name: pane.title,
|
|
103
|
+
timeoutMs: renameBudget,
|
|
104
|
+
});
|
|
91
105
|
}
|
|
92
106
|
/**
|
|
93
107
|
* One pipe, one answer — or null, and the caller polls instead.
|
|
@@ -99,30 +113,47 @@ async function nudgeManifest(client, session, env) {
|
|
|
99
113
|
* --plugin` loads a new copy for each key — that is how a dead bus became
|
|
100
114
|
* dozens of WASM panes. One remembered key, then polling.
|
|
101
115
|
*/
|
|
102
|
-
export async function busSnapshot(client, state, session, clock, env = process.env, payload = "status") {
|
|
116
|
+
export async function busSnapshot(client, state, session, clock, env = process.env, payload = "status", opts = {}) {
|
|
117
|
+
const remaining = () => {
|
|
118
|
+
throwIfAborted(opts.signal);
|
|
119
|
+
return opts.deadlineAt === undefined
|
|
120
|
+
? Infinity
|
|
121
|
+
: Math.max(0, opts.deadlineAt - clock.now());
|
|
122
|
+
};
|
|
123
|
+
if (remaining() <= 0)
|
|
124
|
+
return null;
|
|
103
125
|
const plan = planBus(client, state, env, session);
|
|
104
126
|
const pluginPath = plan.plugin;
|
|
105
|
-
|
|
127
|
+
const url = plan.url;
|
|
128
|
+
if (!plan.enabled || !url || !pluginPath)
|
|
106
129
|
return null;
|
|
107
130
|
const configKey = answeredWith.get(session) ?? plan.configKey;
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
131
|
+
const ask = async () => {
|
|
132
|
+
const budget = Math.min(DEFAULT_BUS_TIMEOUT_MS, remaining());
|
|
133
|
+
if (budget <= 0)
|
|
134
|
+
return null;
|
|
135
|
+
const snapshot = await askOnce(client, session, url, configKey, payload, budget);
|
|
136
|
+
throwIfAborted(opts.signal);
|
|
137
|
+
return snapshot;
|
|
138
|
+
};
|
|
139
|
+
let snapshot = await ask();
|
|
140
|
+
if (snapshot && !snapshot.ready && remaining() > 0) {
|
|
141
|
+
await clock.sleep(Math.min(COLD_RETRY_MS, remaining()));
|
|
142
|
+
snapshot = (await ask()) ?? snapshot;
|
|
114
143
|
}
|
|
115
|
-
if (snapshot && !snapshot.ready) {
|
|
144
|
+
if (snapshot && !snapshot.ready && remaining() > 0) {
|
|
116
145
|
try {
|
|
117
|
-
await nudgeManifest(client, session, env);
|
|
118
|
-
snapshot =
|
|
119
|
-
(await askOnce(client, session, plan.url, configKey, payload)) ??
|
|
120
|
-
snapshot;
|
|
146
|
+
await nudgeManifest(client, session, env, remaining);
|
|
147
|
+
snapshot = (await ask()) ?? snapshot;
|
|
121
148
|
}
|
|
122
149
|
catch {
|
|
150
|
+
throwIfAborted(opts.signal);
|
|
123
151
|
// A failed nudge must not fail the op — the caller polls instead.
|
|
124
152
|
}
|
|
125
153
|
}
|
|
154
|
+
// An expired request says nothing about bus health or an empty cold manifest.
|
|
155
|
+
if (remaining() <= 0 && !snapshot?.ready)
|
|
156
|
+
return null;
|
|
126
157
|
if (snapshot) {
|
|
127
158
|
answeredWith.set(session, configKey);
|
|
128
159
|
return { snapshot, configKey };
|
|
@@ -141,7 +172,7 @@ export async function busSnapshot(client, state, session, clock, env = process.e
|
|
|
141
172
|
* `dump-screen` returns them ragged. `normalizeScreen` erases exactly that
|
|
142
173
|
* difference, and every caller here already normalizes before comparing.
|
|
143
174
|
*/
|
|
144
|
-
export async function busScreens(client, state, session, paneIds, clock, env = process.env) {
|
|
175
|
+
export async function busScreens(client, state, session, paneIds, clock, env = process.env, timeoutMs) {
|
|
145
176
|
if (paneIds.length < 2)
|
|
146
177
|
return null;
|
|
147
178
|
const plan = planBus(client, state, env, session);
|
|
@@ -153,6 +184,7 @@ export async function busScreens(client, state, session, paneIds, clock, env = p
|
|
|
153
184
|
url: plan.url,
|
|
154
185
|
configKey,
|
|
155
186
|
panes: paneIds,
|
|
187
|
+
timeoutMs,
|
|
156
188
|
});
|
|
157
189
|
const parsed = parseScrollbackReply(reply.stdout);
|
|
158
190
|
if (!parsed || !parsed.ready)
|
|
@@ -194,7 +226,7 @@ export async function busWait(client, state, session, request, env = process.env
|
|
|
194
226
|
* `status` answers by sampling twice, without the gap between samples — but
|
|
195
227
|
* relative to the previous call rather than a fixed window.
|
|
196
228
|
*/
|
|
197
|
-
export async function busChanged(client, state, session, paneIds, env = process.env) {
|
|
229
|
+
export async function busChanged(client, state, session, paneIds, env = process.env, timeoutMs) {
|
|
198
230
|
if (paneIds.length === 0)
|
|
199
231
|
return null;
|
|
200
232
|
const plan = planBus(client, state, env, session);
|
|
@@ -206,6 +238,7 @@ export async function busChanged(client, state, session, paneIds, env = process.
|
|
|
206
238
|
url: plan.url,
|
|
207
239
|
configKey,
|
|
208
240
|
panes: paneIds,
|
|
241
|
+
timeoutMs,
|
|
209
242
|
});
|
|
210
243
|
const parsed = parseChangedReply(reply.stdout);
|
|
211
244
|
if (!parsed || !parsed.ready)
|
package/dist/ops/dispatch.d.ts
CHANGED
|
@@ -1,4 +1,10 @@
|
|
|
1
1
|
import { type ZellijClient } from "../zellij/client.js";
|
|
2
2
|
import type { DispatchDeps, OpsResult } from "./types.js";
|
|
3
|
-
/**
|
|
3
|
+
/**
|
|
4
|
+
* Per-invocation routing from `--local` / `--ssh`. `--local` clears both SSH
|
|
5
|
+
* and serve (and remote IPC). `--ssh` sets the destination for this call and
|
|
6
|
+
* clears serve so the flag wins over a sticky ZSWARM_SERVE.
|
|
7
|
+
*/
|
|
8
|
+
export declare function resolveInvocationEnv(args: Record<string, unknown>, base: NodeJS.ProcessEnv): NodeJS.ProcessEnv;
|
|
9
|
+
/** Shared MCP/CLI dispatch, with invocation routing on successes and failures. */
|
|
4
10
|
export declare function dispatchZswarm(args: Record<string, unknown>, injected?: ZellijClient, deps?: DispatchDeps): Promise<OpsResult>;
|