@zswarm/core 0.6.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 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"]);
@@ -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. */
@@ -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, timeoutMs: 60_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 res = await call(args, localEnv(env)).catch((err) => ({ ok: false, error: { code: "failed", message: String(err) } }));
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
@@ -39,6 +39,6 @@ export { findPython, runSlot, slotScriptPath } from "./host/slot.js";
39
39
  export { installServeService, serveServiceName, serveServiceSpec, serveTokenFromEnv, serveTokenPath, uninstallServeService, SERVE_AWAIT_SESSION_ENV, SERVE_TOKEN_FILE_ENV, type ServeServiceInput, } from "./host/serve-service.js";
40
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";
41
41
  export { crewCheck, crewClient, crewDir, crewDown, crewServiceSpecs, crewStart, crewStatus, crewUp, firstLayoutPane, layoutPaneNames, parseEnvFile, type CrewDeps, type CrewUpInput, } from "./host/crew.js";
42
- 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";
43
43
  export { hostChecks, hostDoctor, hostInstall, hostInstallArgs, hostScriptPath, parseHostReport, type HostCheck, type HostDoctorInput, type HostInstallInput, type SshRunner, } from "./host/host.js";
44
44
  export { isSelfPane } from "./ops/guards.js";
package/dist/index.js CHANGED
@@ -37,6 +37,6 @@ export { findPython, runSlot, slotScriptPath } from "./host/slot.js";
37
37
  export { installServeService, serveServiceName, serveServiceSpec, serveTokenFromEnv, serveTokenPath, uninstallServeService, SERVE_AWAIT_SESSION_ENV, SERVE_TOKEN_FILE_ENV, } from "./host/serve-service.js";
38
38
  export { accountArgv, installServices, launchdLabel, launchdPlist, removeServices, serviceAccount, serviceStates, systemdUnitName, systemdUnits, writeAccountFile, } from "./host/services.js";
39
39
  export { crewCheck, crewClient, crewDir, crewDown, crewServiceSpecs, crewStart, crewStatus, crewUp, firstLayoutPane, layoutPaneNames, parseEnvFile, } from "./host/crew.js";
40
- 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";
41
41
  export { hostChecks, hostDoctor, hostInstall, hostInstallArgs, hostScriptPath, parseHostReport, } from "./host/host.js";
42
42
  export { isSelfPane } from "./ops/guards.js";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zswarm/core",
3
- "version": "0.6.0",
3
+ "version": "0.7.0",
4
4
  "type": "module",
5
5
  "description": "zSwarm Zellij client and shared ops dispatch",
6
6
  "exports": {