@zswarm/core 0.1.5 → 0.1.7
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 +47 -2
- package/dist/exec.js +249 -20
- package/dist/harness.js +12 -7
- package/dist/index.d.ts +8 -6
- package/dist/index.js +5 -4
- package/dist/ops/broadcast.js +1 -1
- package/dist/ops/bus.d.ts +6 -3
- package/dist/ops/bus.js +85 -35
- package/dist/ops/dispatch.d.ts +7 -1
- package/dist/ops/dispatch.js +235 -70
- 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/panes.js +4 -4
- 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 +245 -96
- 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/ops/worktree.js +2 -2
- package/dist/schema.d.ts +2 -0
- package/dist/schema.js +60 -9
- package/dist/zellij/args.d.ts +2 -0
- package/dist/zellij/binary.d.ts +47 -0
- package/dist/zellij/binary.js +201 -8
- package/dist/zellij/bus.d.ts +2 -0
- package/dist/zellij/bus.js +3 -2
- package/dist/zellij/cache.d.ts +24 -0
- package/dist/zellij/cache.js +90 -0
- package/dist/zellij/client.d.ts +37 -6
- package/dist/zellij/client.js +181 -42
- 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
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { type RemoteShell } from "./zellij/ipc.js";
|
|
1
|
+
import { type IpcDirs, type RemoteShell } from "./zellij/ipc.js";
|
|
2
2
|
export type ExecResult = {
|
|
3
3
|
code: number;
|
|
4
4
|
stdout: string;
|
|
@@ -39,9 +39,54 @@ 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
|
+
/**
|
|
53
|
+
* Resolve `ZSWARM_TMP=auto` once under this caller's budget before parallel
|
|
54
|
+
* identity/capability probes. Independent of other callers' in-flight work.
|
|
55
|
+
*/
|
|
56
|
+
prepareIpc: (timeoutMs: number, signal?: AbortSignal) => Promise<IpcDirs | undefined>;
|
|
57
|
+
};
|
|
58
|
+
/** Positive `auto` IPC hits only; failures/cancels never enter this map. */
|
|
59
|
+
export declare const DEFAULT_IPC_DISCOVERY_TTL_MS = 15000;
|
|
60
|
+
export declare const DEFAULT_IPC_DISCOVERY_CACHE_LIMIT = 512;
|
|
61
|
+
/** Fully scoped key so hosts / SSH bins / remote shells / users never collide. */
|
|
62
|
+
export declare function ipcDiscoveryCacheKey(target: SshTarget, env?: NodeJS.ProcessEnv): string;
|
|
63
|
+
/**
|
|
64
|
+
* Probe list for `ZSWARM_TMP=auto`. Known Windows (cmd / interactive / .exe)
|
|
65
|
+
* skips the Unix `ps` fallback; explicit `sh` skips PowerShell. Unknown
|
|
66
|
+
* `zellij` still tries both — a Windows host with remoteBin=zellij looks Unix.
|
|
67
|
+
*/
|
|
68
|
+
export declare function ipcDiscoveryProbes(target: SshTarget): string[];
|
|
69
|
+
export declare function resetIpcDiscoveryCache(): void;
|
|
70
|
+
/** Test helper: shrink the positive-hit TTL without exposing the map. */
|
|
71
|
+
export declare function setIpcDiscoveryTtlMs(ttlMs: number): void;
|
|
72
|
+
export declare function peekIpcDiscoveryCache(key: string): IpcDirs | undefined;
|
|
73
|
+
/**
|
|
74
|
+
* Canonical SSH + resolved IPC namespace. Listing caches should hash this
|
|
75
|
+
* (or `ipcState.tmp` / `socketDir` after a refreshed read) so a warmed
|
|
76
|
+
* socket context matches across `createSshExec` instances.
|
|
77
|
+
*/
|
|
78
|
+
export declare function sshIpcContextKey(target: SshTarget, env?: NodeJS.ProcessEnv, ipc?: {
|
|
79
|
+
tmp?: string;
|
|
80
|
+
socketDir?: string;
|
|
81
|
+
} | null): string;
|
|
82
|
+
/** Test helper: shrink the positive-hit map so eviction is observable. */
|
|
83
|
+
export declare function setIpcDiscoveryCacheLimit(limit: number): void;
|
|
84
|
+
export declare function ipcDiscoveryCacheSize(): number;
|
|
42
85
|
/**
|
|
43
86
|
* Run the remote zellij over ssh. The whole invocation is quoted into a single
|
|
44
87
|
* command string, because the remote login shell re-parses it.
|
|
45
88
|
*/
|
|
46
|
-
export declare function createSshExec(target: SshTarget, env: NodeJS.ProcessEnv
|
|
89
|
+
export declare function createSshExec(target: SshTarget, env: NodeJS.ProcessEnv, options?: {
|
|
90
|
+
pinIpc?: boolean;
|
|
91
|
+
}): SshExecFn;
|
|
47
92
|
export declare function createExec(binPath: string, env: NodeJS.ProcessEnv): ExecFn;
|
package/dist/exec.js
CHANGED
|
@@ -35,21 +35,118 @@ export function buildSshRemoteCommand(target, args, tmp, timeoutMs, socketDir) {
|
|
|
35
35
|
}
|
|
36
36
|
return withTmp;
|
|
37
37
|
}
|
|
38
|
-
|
|
38
|
+
/** Positive `auto` IPC hits only; failures/cancels never enter this map. */
|
|
39
|
+
export const DEFAULT_IPC_DISCOVERY_TTL_MS = 15_000;
|
|
40
|
+
export const DEFAULT_IPC_DISCOVERY_CACHE_LIMIT = 512;
|
|
41
|
+
const ipcDiscoveryCache = new Map();
|
|
42
|
+
let ipcDiscoveryTtlMs = DEFAULT_IPC_DISCOVERY_TTL_MS;
|
|
43
|
+
let ipcDiscoveryCacheLimit = DEFAULT_IPC_DISCOVERY_CACHE_LIMIT;
|
|
44
|
+
const IPC_USER_ENV = [
|
|
45
|
+
"USER",
|
|
46
|
+
"USERNAME",
|
|
47
|
+
"LOGNAME",
|
|
48
|
+
"HOME",
|
|
49
|
+
"USERPROFILE",
|
|
50
|
+
"USERDOMAIN",
|
|
51
|
+
"SSH_AUTH_SOCK",
|
|
52
|
+
"PATH",
|
|
53
|
+
"Path",
|
|
54
|
+
"XDG_CONFIG_HOME",
|
|
55
|
+
];
|
|
56
|
+
/** Fully scoped key so hosts / SSH bins / remote shells / users never collide. */
|
|
57
|
+
export function ipcDiscoveryCacheKey(target, env = {}) {
|
|
58
|
+
const requested = target.tmp?.trim() ?? "";
|
|
59
|
+
const user = IPC_USER_ENV.map((name) => env[name] ?? "");
|
|
60
|
+
return JSON.stringify([
|
|
61
|
+
target.ssh,
|
|
62
|
+
target.host,
|
|
63
|
+
target.options,
|
|
64
|
+
target.remoteBin,
|
|
65
|
+
target.remoteShell ?? "",
|
|
66
|
+
target.mode ?? "ssh",
|
|
67
|
+
requested.toLowerCase() === "auto" ? "auto" : requested,
|
|
68
|
+
user,
|
|
69
|
+
]);
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* Probe list for `ZSWARM_TMP=auto`. Known Windows (cmd / interactive / .exe)
|
|
73
|
+
* skips the Unix `ps` fallback; explicit `sh` skips PowerShell. Unknown
|
|
74
|
+
* `zellij` still tries both — a Windows host with remoteBin=zellij looks Unix.
|
|
75
|
+
*/
|
|
76
|
+
export function ipcDiscoveryProbes(target) {
|
|
39
77
|
const shell = inferRemoteShell({
|
|
40
78
|
explicit: target.remoteShell,
|
|
41
79
|
remoteBin: target.remoteBin,
|
|
42
80
|
mode: target.mode,
|
|
43
81
|
});
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
82
|
+
const windows = windowsDiscoverRemote();
|
|
83
|
+
const unix = unixDiscoverRemote();
|
|
84
|
+
if (shell === "cmd")
|
|
85
|
+
return [windows];
|
|
86
|
+
if (target.remoteShell === "sh")
|
|
87
|
+
return [unix];
|
|
88
|
+
return [unix, windows];
|
|
89
|
+
}
|
|
90
|
+
export function resetIpcDiscoveryCache() {
|
|
91
|
+
ipcDiscoveryCache.clear();
|
|
92
|
+
ipcDiscoveryTtlMs = DEFAULT_IPC_DISCOVERY_TTL_MS;
|
|
93
|
+
ipcDiscoveryCacheLimit = DEFAULT_IPC_DISCOVERY_CACHE_LIMIT;
|
|
94
|
+
}
|
|
95
|
+
/** Test helper: shrink the positive-hit TTL without exposing the map. */
|
|
96
|
+
export function setIpcDiscoveryTtlMs(ttlMs) {
|
|
97
|
+
ipcDiscoveryTtlMs = Math.max(0, ttlMs);
|
|
98
|
+
}
|
|
99
|
+
export function peekIpcDiscoveryCache(key) {
|
|
100
|
+
const entry = ipcDiscoveryCache.get(key);
|
|
101
|
+
if (!entry)
|
|
102
|
+
return undefined;
|
|
103
|
+
if (entry.expiresAt <= Date.now()) {
|
|
104
|
+
ipcDiscoveryCache.delete(key);
|
|
105
|
+
return undefined;
|
|
106
|
+
}
|
|
107
|
+
return { tmp: entry.dirs.tmp, socketDir: entry.dirs.socketDir };
|
|
108
|
+
}
|
|
109
|
+
/**
|
|
110
|
+
* Canonical SSH + resolved IPC namespace. Listing caches should hash this
|
|
111
|
+
* (or `ipcState.tmp` / `socketDir` after a refreshed read) so a warmed
|
|
112
|
+
* socket context matches across `createSshExec` instances.
|
|
113
|
+
*/
|
|
114
|
+
export function sshIpcContextKey(target, env = {}, ipc) {
|
|
115
|
+
return JSON.stringify([ipcDiscoveryCacheKey(target, env), ipc?.tmp ?? "", ipc?.socketDir ?? ""]);
|
|
116
|
+
}
|
|
117
|
+
/** Test helper: shrink the positive-hit map so eviction is observable. */
|
|
118
|
+
export function setIpcDiscoveryCacheLimit(limit) {
|
|
119
|
+
ipcDiscoveryCacheLimit = Math.max(1, limit);
|
|
120
|
+
}
|
|
121
|
+
export function ipcDiscoveryCacheSize() {
|
|
122
|
+
return ipcDiscoveryCache.size;
|
|
123
|
+
}
|
|
124
|
+
function storeIpcDiscovery(key, dirs) {
|
|
125
|
+
ipcDiscoveryCache.delete(key);
|
|
126
|
+
ipcDiscoveryCache.set(key, {
|
|
127
|
+
dirs: { ...dirs },
|
|
128
|
+
expiresAt: Date.now() + ipcDiscoveryTtlMs,
|
|
129
|
+
});
|
|
130
|
+
while (ipcDiscoveryCache.size > ipcDiscoveryCacheLimit) {
|
|
131
|
+
ipcDiscoveryCache.delete(ipcDiscoveryCache.keys().next().value);
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
async function discoverRemoteIpc(runner, target, remaining, signal) {
|
|
135
|
+
const probes = ipcDiscoveryProbes(target);
|
|
49
136
|
for (const probe of probes) {
|
|
137
|
+
if (signal?.aborted)
|
|
138
|
+
return undefined;
|
|
139
|
+
const left = remaining();
|
|
140
|
+
if (left <= 0)
|
|
141
|
+
return undefined;
|
|
50
142
|
const result = await runner([...target.options, target.host, probe], {
|
|
51
|
-
timeoutMs,
|
|
143
|
+
timeoutMs: left,
|
|
144
|
+
signal,
|
|
52
145
|
});
|
|
146
|
+
if (result.code !== 0)
|
|
147
|
+
continue;
|
|
148
|
+
if (remaining() <= 0)
|
|
149
|
+
return undefined;
|
|
53
150
|
const dirs = pickIpcDirs(parseZellijServerPaths(result.stdout));
|
|
54
151
|
if (dirs)
|
|
55
152
|
return dirs;
|
|
@@ -60,28 +157,160 @@ async function discoverRemoteIpc(runner, target, timeoutMs) {
|
|
|
60
157
|
* Run the remote zellij over ssh. The whole invocation is quoted into a single
|
|
61
158
|
* command string, because the remote login shell re-parses it.
|
|
62
159
|
*/
|
|
63
|
-
export function createSshExec(target, env) {
|
|
160
|
+
export function createSshExec(target, env, options = {}) {
|
|
64
161
|
const runner = createExec(target.ssh, env);
|
|
65
|
-
|
|
66
|
-
|
|
162
|
+
const cacheKey = ipcDiscoveryCacheKey(target, env);
|
|
163
|
+
let autoFailed = false;
|
|
164
|
+
let pinnedIpc;
|
|
165
|
+
const ipcState = {
|
|
166
|
+
requested: null,
|
|
167
|
+
status: "none",
|
|
168
|
+
};
|
|
169
|
+
function adopt(dirs) {
|
|
170
|
+
if (options.pinIpc) {
|
|
171
|
+
pinnedIpc ??= { ...dirs };
|
|
172
|
+
dirs = pinnedIpc;
|
|
173
|
+
}
|
|
174
|
+
autoFailed = false;
|
|
175
|
+
ipcState.status = "resolved";
|
|
176
|
+
ipcState.tmp = dirs.tmp;
|
|
177
|
+
ipcState.socketDir = dirs.socketDir;
|
|
178
|
+
return { ...dirs };
|
|
179
|
+
}
|
|
180
|
+
/** Seed from a positive hit; unpinned execs drop resolved dirs after TTL. */
|
|
181
|
+
function refreshIpcView() {
|
|
182
|
+
const requested = target.tmp?.trim() || null;
|
|
183
|
+
ipcState.requested = requested;
|
|
184
|
+
if (!requested) {
|
|
185
|
+
ipcState.status = "none";
|
|
186
|
+
delete ipcState.tmp;
|
|
187
|
+
delete ipcState.socketDir;
|
|
188
|
+
return ipcState;
|
|
189
|
+
}
|
|
190
|
+
if (requested.toLowerCase() !== "auto") {
|
|
191
|
+
ipcState.status = "skipped";
|
|
192
|
+
ipcState.tmp = requested;
|
|
193
|
+
ipcState.socketDir = "";
|
|
194
|
+
return ipcState;
|
|
195
|
+
}
|
|
196
|
+
// A high-level client pins its namespace so pane lookup and the following
|
|
197
|
+
// write cannot switch sockets when another caller refreshes discovery.
|
|
198
|
+
if (pinnedIpc)
|
|
199
|
+
return ipcState;
|
|
200
|
+
// Diagnostics describe this exec's last attempt, not a sibling's later
|
|
201
|
+
// success. A subsequent actual invocation may adopt a positive cache hit.
|
|
202
|
+
if (autoFailed)
|
|
203
|
+
return ipcState;
|
|
204
|
+
const cached = peekIpcDiscoveryCache(cacheKey);
|
|
205
|
+
if (cached) {
|
|
206
|
+
adopt(cached);
|
|
207
|
+
return ipcState;
|
|
208
|
+
}
|
|
209
|
+
if (ipcState.status === "resolved") {
|
|
210
|
+
ipcState.status = "none";
|
|
211
|
+
delete ipcState.tmp;
|
|
212
|
+
delete ipcState.socketDir;
|
|
213
|
+
}
|
|
214
|
+
return ipcState;
|
|
215
|
+
}
|
|
216
|
+
refreshIpcView();
|
|
217
|
+
async function resolveIpc(remaining, signal) {
|
|
67
218
|
const requested = target.tmp?.trim();
|
|
68
|
-
if (!requested)
|
|
219
|
+
if (!requested) {
|
|
220
|
+
ipcState.requested = null;
|
|
221
|
+
ipcState.status = "none";
|
|
69
222
|
return undefined;
|
|
223
|
+
}
|
|
224
|
+
ipcState.requested = requested;
|
|
70
225
|
if (requested.toLowerCase() !== "auto") {
|
|
226
|
+
ipcState.status = "skipped";
|
|
227
|
+
ipcState.tmp = requested;
|
|
228
|
+
ipcState.socketDir = "";
|
|
71
229
|
return { tmp: requested, socketDir: "" };
|
|
72
230
|
}
|
|
231
|
+
if (pinnedIpc)
|
|
232
|
+
return adopt(pinnedIpc);
|
|
233
|
+
const cached = peekIpcDiscoveryCache(cacheKey);
|
|
73
234
|
if (cached)
|
|
74
|
-
return cached;
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
235
|
+
return adopt(cached);
|
|
236
|
+
// Do not re-probe after a failed/expired/cancelled attempt on this exec.
|
|
237
|
+
// A later createSshExec with the same key still probes — failures are not
|
|
238
|
+
// stored, and in-flight work is never shared across callers' budgets.
|
|
239
|
+
if (autoFailed)
|
|
240
|
+
return undefined;
|
|
241
|
+
if (signal?.aborted) {
|
|
242
|
+
autoFailed = true;
|
|
243
|
+
ipcState.status = "cancelled";
|
|
244
|
+
return undefined;
|
|
245
|
+
}
|
|
246
|
+
if (remaining() <= 0) {
|
|
247
|
+
autoFailed = true;
|
|
248
|
+
ipcState.status = "expired";
|
|
249
|
+
return undefined;
|
|
250
|
+
}
|
|
251
|
+
const dirs = await discoverRemoteIpc(runner, target, remaining, signal);
|
|
252
|
+
// Another probe on this client may have pinned a namespace while we were
|
|
253
|
+
// waiting. Its first positive result wins, even if this probe failed.
|
|
254
|
+
if (pinnedIpc)
|
|
255
|
+
return adopt(pinnedIpc);
|
|
256
|
+
if (signal?.aborted) {
|
|
257
|
+
autoFailed = true;
|
|
258
|
+
ipcState.status = "cancelled";
|
|
259
|
+
return undefined;
|
|
260
|
+
}
|
|
261
|
+
if (dirs) {
|
|
262
|
+
if (remaining() <= 0) {
|
|
263
|
+
autoFailed = true;
|
|
264
|
+
ipcState.status = "expired";
|
|
265
|
+
return undefined;
|
|
266
|
+
}
|
|
267
|
+
storeIpcDiscovery(cacheKey, dirs);
|
|
268
|
+
return adopt(dirs);
|
|
269
|
+
}
|
|
270
|
+
autoFailed = true;
|
|
271
|
+
ipcState.status = remaining() <= 0 ? "expired" : "failed";
|
|
272
|
+
return undefined;
|
|
79
273
|
}
|
|
80
|
-
|
|
81
|
-
const
|
|
82
|
-
const
|
|
83
|
-
|
|
274
|
+
const exec = (async (args, options) => {
|
|
275
|
+
const deadline = Date.now() + options.timeoutMs;
|
|
276
|
+
const remaining = () => Math.max(0, deadline - Date.now());
|
|
277
|
+
const ipc = await resolveIpc(remaining, options.signal);
|
|
278
|
+
if (options.signal?.aborted) {
|
|
279
|
+
return {
|
|
280
|
+
code: -1,
|
|
281
|
+
stdout: "",
|
|
282
|
+
stderr: `${target.ssh} cancelled`,
|
|
283
|
+
};
|
|
284
|
+
}
|
|
285
|
+
const left = remaining();
|
|
286
|
+
// Do not launch the target command after the caller's budget is gone —
|
|
287
|
+
// especially after two discovery probes ate the whole timeout.
|
|
288
|
+
if (left <= 0) {
|
|
289
|
+
return {
|
|
290
|
+
code: -1,
|
|
291
|
+
stdout: "",
|
|
292
|
+
stderr: `${target.ssh} timed out after ${options.timeoutMs}ms`,
|
|
293
|
+
};
|
|
294
|
+
}
|
|
295
|
+
// auto discovery failed: still run, but callers read ipcState for reachability.
|
|
296
|
+
const remote = buildSshRemoteCommand(target, args, ipc?.tmp, left, ipc?.socketDir);
|
|
297
|
+
return runner([...target.options, target.host, remote], {
|
|
298
|
+
...options,
|
|
299
|
+
timeoutMs: left,
|
|
300
|
+
});
|
|
301
|
+
});
|
|
302
|
+
Object.defineProperty(exec, "ipcState", {
|
|
303
|
+
enumerable: true,
|
|
304
|
+
configurable: true,
|
|
305
|
+
get() {
|
|
306
|
+
return { ...refreshIpcView() };
|
|
307
|
+
},
|
|
308
|
+
});
|
|
309
|
+
exec.prepareIpc = (timeoutMs, signal) => {
|
|
310
|
+
const deadline = Date.now() + timeoutMs;
|
|
311
|
+
return resolveIpc(() => Math.max(0, deadline - Date.now()), signal);
|
|
84
312
|
};
|
|
313
|
+
return exec;
|
|
85
314
|
}
|
|
86
315
|
/** Read stdout until the answer is in hand, then stop caring about the child. */
|
|
87
316
|
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,27 @@
|
|
|
1
1
|
export { ZellijError } from "./errors.js";
|
|
2
|
+
export { ListingCache } from "./zellij/cache.js";
|
|
2
3
|
export { createExec, NOT_FOUND_EXIT, type ExecFn, type ExecOptions, type ExecResult, } from "./exec.js";
|
|
3
4
|
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";
|
|
5
|
+
export { expandHomePath, looksLikeZswarmBinary, isZellijVersionOutput, resolveZellijBinary, resetZellijIdentityCache, sanitizeZellijEnv, validateSshDestination, validateSshMode, type ZellijExecFn, type ZellijExecResult, } from "./zellij/binary.js";
|
|
5
6
|
export { normalizePaneId, parsePaneList, resolvePane, type ZellijPane, } from "./zellij/panes.js";
|
|
6
|
-
export { parseSessionList, resolveSelfPaneId, type ZellijSessionResolve, } from "./zellij/session.js";
|
|
7
|
+
export { liveSessionNames, parseSessionList, resolveSelfPaneId, type ZellijSession, type ZellijSessionResolve, } from "./zellij/session.js";
|
|
7
8
|
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
9
|
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
10
|
export { busSnapshot, closeBusPluginPanes, planBus, resetBusCache, type BusPlan, } from "./ops/bus.js";
|
|
10
|
-
export { createZellijClient, type ZellijClient, type ZellijClientOptions, } from "./zellij/client.js";
|
|
11
|
+
export { createZellijClient, type ZellijClient, type ZellijClientOptions, type ZellijTransport, } from "./zellij/client.js";
|
|
11
12
|
export { createStateStore, defaultStateDir, type LogEntry, type SignalChannel, type StateStore, type StateStoreOptions, } from "./state.js";
|
|
12
|
-
export { dispatchZswarm } from "./ops/dispatch.js";
|
|
13
|
+
export { dispatchZswarm, resolveInvocationEnv } from "./ops/dispatch.js";
|
|
13
14
|
export { assertOpAllowed, assertPaneAllowed, isWriteOp, loadPolicy, type Policy, } from "./policy.js";
|
|
14
|
-
export { buildSshRemoteCommand, createSshExec, quoteRemoteArg, shellQuote, type SshTarget, } from "./exec.js";
|
|
15
|
+
export { buildSshRemoteCommand, createSshExec, quoteRemoteArg, shellQuote, type IpcDiscoveryState, type SshExecFn, type SshTarget, } from "./exec.js";
|
|
15
16
|
export { resolveSshTarget } from "./zellij/binary.js";
|
|
16
17
|
export { applyIpcTmpEnv, cmdQuote, concreteTmp, encodePowerShellCommand, inferRemoteShell, parseZellijServerPaths, pickIpcDirs, pickIpcTmp, socketDirFromServerPath, tmpFromServerPath, unixDiscoverRemote, windowsDiscoverRemote, windowsInteractiveRemote, wrapWithTmpEnv, } from "./zellij/ipc.js";
|
|
17
18
|
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
19
|
export { parseTabList, resolveTab, type ZellijTab } from "./zellij/tabs.js";
|
|
19
20
|
export { selectTargets } from "./ops/broadcast.js";
|
|
20
21
|
export { diffScreens, cursorKey } from "./ops/tail.js";
|
|
21
|
-
export { classify, lastLine, type PeerState } from "./ops/status.js";
|
|
22
|
+
export { classify, lastLine, mapPool, peerStatus, DEFAULT_STATUS_TIMEOUT_MS, STATUS_DUMP_CONCURRENCY, type PeerState, } from "./ops/status.js";
|
|
22
23
|
export { normalizeScreen, truncateDumpText, DEFAULT_DUMP_MAX_CHARS, DEFAULT_WAIT_MAX_CHARS, } from "./ops/util.js";
|
|
24
|
+
export type { RoutingContext } from "./ops/routing.js";
|
|
23
25
|
export type { DispatchDeps, OpsResult } from "./ops/types.js";
|
|
24
26
|
export { normalizeKey, normalizeKeys, tokenizeCommand } from "./keys.js";
|
|
25
27
|
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,16 @@
|
|
|
1
1
|
export { ZellijError } from "./errors.js";
|
|
2
|
+
export { ListingCache } from "./zellij/cache.js";
|
|
2
3
|
export { createExec, NOT_FOUND_EXIT, } from "./exec.js";
|
|
3
4
|
export { createGitClient, defaultWorktreeRoot, normalizeRepoPath, parseWorktreeList, worktreeDirName, } from "./git.js";
|
|
4
|
-
export { expandHomePath, resolveZellijBinary, sanitizeZellijEnv, } from "./zellij/binary.js";
|
|
5
|
+
export { expandHomePath, looksLikeZswarmBinary, isZellijVersionOutput, resolveZellijBinary, resetZellijIdentityCache, sanitizeZellijEnv, validateSshDestination, validateSshMode, } from "./zellij/binary.js";
|
|
5
6
|
export { normalizePaneId, parsePaneList, resolvePane, } from "./zellij/panes.js";
|
|
6
|
-
export { parseSessionList, resolveSelfPaneId, } from "./zellij/session.js";
|
|
7
|
+
export { liveSessionNames, parseSessionList, resolveSelfPaneId, } from "./zellij/session.js";
|
|
7
8
|
export { buildClosePaneArgs, buildDumpArgs, buildLaunchPluginArgs, buildListPanesArgs, buildNewPaneArgs, buildNewTabArgs, buildPasteArgs, buildPipeArgs, buildSendEnterArgs, buildSendKeysArgs, buildWriteCharsArgs, scrollbackPayload, waitPayload, } from "./zellij/args.js";
|
|
8
9
|
export { busPluginUrl, busToPanes, isBusPluginPane, nextConfigKey, parseBusReply, parseScrollbackReply, resolveBusPlugin, scrollbackToScreen, BUS_PIPE_NAME, DEFAULT_BUS_KEY, DEFAULT_BUS_TIMEOUT_MS, } from "./zellij/bus.js";
|
|
9
10
|
export { busSnapshot, closeBusPluginPanes, planBus, resetBusCache, } from "./ops/bus.js";
|
|
10
11
|
export { createZellijClient, } from "./zellij/client.js";
|
|
11
12
|
export { createStateStore, defaultStateDir, } from "./state.js";
|
|
12
|
-
export { dispatchZswarm } from "./ops/dispatch.js";
|
|
13
|
+
export { dispatchZswarm, resolveInvocationEnv } from "./ops/dispatch.js";
|
|
13
14
|
export { assertOpAllowed, assertPaneAllowed, isWriteOp, loadPolicy, } from "./policy.js";
|
|
14
15
|
export { buildSshRemoteCommand, createSshExec, quoteRemoteArg, shellQuote, } from "./exec.js";
|
|
15
16
|
export { resolveSshTarget } from "./zellij/binary.js";
|
|
@@ -18,7 +19,7 @@ export { callServe, DEFAULT_SERVE_LISTEN, installServeLogon, isLoopbackHost, par
|
|
|
18
19
|
export { parseTabList, resolveTab } from "./zellij/tabs.js";
|
|
19
20
|
export { selectTargets } from "./ops/broadcast.js";
|
|
20
21
|
export { diffScreens, cursorKey } from "./ops/tail.js";
|
|
21
|
-
export { classify, lastLine } from "./ops/status.js";
|
|
22
|
+
export { classify, lastLine, mapPool, peerStatus, DEFAULT_STATUS_TIMEOUT_MS, STATUS_DUMP_CONCURRENCY, } from "./ops/status.js";
|
|
22
23
|
export { normalizeScreen, truncateDumpText, DEFAULT_DUMP_MAX_CHARS, DEFAULT_WAIT_MAX_CHARS, } from "./ops/util.js";
|
|
23
24
|
export { normalizeKey, normalizeKeys, tokenizeCommand } from "./keys.js";
|
|
24
25
|
export { cliUsage, mcpInputSchema, parseCliArgv, MCP_TOOL_DESCRIPTION, OP_NAMES, PARAMS, TARGET_OPS, } from "./schema.js";
|
package/dist/ops/broadcast.js
CHANGED
|
@@ -70,7 +70,7 @@ export async function broadcast(client, state, args, clock, policy, env = proces
|
|
|
70
70
|
if (!body.trim())
|
|
71
71
|
throw new ZellijError("missing_body", "body required");
|
|
72
72
|
const { session } = await client.resolveSession(typeof args.session === "string" ? args.session : undefined);
|
|
73
|
-
const panes = await client.listPanes(session);
|
|
73
|
+
const panes = await client.listPanes(session, undefined, { fresh: true });
|
|
74
74
|
const labeled = withSenderLabel(args, {
|
|
75
75
|
env,
|
|
76
76
|
selfTitle: selfPaneTitle(client, panes),
|
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. */
|