@sealant/mend 0.34.2 → 0.34.5
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/{chunk-JJ66V76Y.js → chunk-5DORQXQT.js} +106 -7
- package/dist/{dashboard-OADX3PHI.js → dashboard-KZ4XGZ6W.js} +32 -13
- package/dist/main.js +378 -89
- package/man/mend-accounts.1 +1 -1
- package/man/mend-adopt.1 +1 -1
- package/man/mend-attach.1 +1 -1
- package/man/mend-codex.1 +1 -1
- package/man/mend-completions.1 +1 -1
- package/man/mend-connect.1 +1 -1
- package/man/mend-continue.1 +1 -1
- package/man/mend-doctor.1 +1 -1
- package/man/mend-dotfiles-repo.1 +1 -1
- package/man/mend-dotfiles-sync.1 +1 -1
- package/man/mend-dotfiles.1 +1 -1
- package/man/mend-env-cluster.1 +1 -1
- package/man/mend-env-load.1 +1 -1
- package/man/mend-env-show.1 +1 -1
- package/man/mend-folder-create.1 +1 -1
- package/man/mend-folder-list.1 +1 -1
- package/man/mend-folder-push.1 +1 -1
- package/man/mend-folder-rm.1 +1 -1
- package/man/mend-git-author.1 +1 -1
- package/man/mend-help.1 +1 -1
- package/man/mend-invite.1 +1 -1
- package/man/mend-keys-init.1 +1 -1
- package/man/mend-keys-mode.1 +1 -1
- package/man/mend-keys-share.1 +1 -1
- package/man/mend-keys-show.1 +1 -1
- package/man/mend-land.1 +1 -1
- package/man/mend-login.1 +1 -1
- package/man/mend-logout.1 +1 -1
- package/man/mend-man.1 +1 -1
- package/man/mend-members.1 +1 -1
- package/man/mend-operator-exposure.1 +1 -1
- package/man/mend-operator-gate.1 +1 -1
- package/man/mend-operator-grant-owner.1 +1 -1
- package/man/mend-operator-org-create.1 +1 -1
- package/man/mend-operator-org-invite-owner.1 +1 -1
- package/man/mend-operator-org-list.1 +1 -1
- package/man/mend-operator-org-rename.1 +1 -1
- package/man/mend-operator-reset-link.1 +1 -1
- package/man/mend-pair.1 +1 -1
- package/man/mend-projects.1 +1 -1
- package/man/mend-pull.1 +1 -1
- package/man/mend-refresh.1 +1 -1
- package/man/mend-rejoin.1 +1 -1
- package/man/mend-resume.1 +1 -1
- package/man/mend-run.1 +1 -1
- package/man/mend-server-logs.1 +1 -1
- package/man/mend-server-restart.1 +1 -1
- package/man/mend-server-setup.1 +1 -1
- package/man/mend-server-start.1 +1 -1
- package/man/mend-server-status.1 +1 -1
- package/man/mend-server-stop.1 +1 -1
- package/man/mend-server-upgrade.1 +1 -1
- package/man/mend-server.1 +1 -1
- package/man/mend-service-add.1 +1 -1
- package/man/mend-service-connect.1 +1 -1
- package/man/mend-service-init.1 +1 -1
- package/man/mend-service-list.1 +1 -1
- package/man/mend-service-logs.1 +1 -1
- package/man/mend-service-restart.1 +1 -1
- package/man/mend-service-run.1 +1 -1
- package/man/mend-service-stop.1 +1 -1
- package/man/mend-session-share.1 +1 -1
- package/man/mend-sessions.1 +1 -1
- package/man/mend-shell.1 +1 -1
- package/man/mend-skills-push.1 +1 -1
- package/man/mend-skills.1 +1 -1
- package/man/mend-snake.1 +1 -1
- package/man/mend-ssh-setup.1 +1 -1
- package/man/mend-ssh.1 +1 -1
- package/man/mend-stop.1 +1 -1
- package/man/mend-ui.1 +1 -1
- package/man/mend-uninstall.1 +1 -1
- package/man/mend-version.1 +1 -1
- package/man/mend-worktrees.1 +1 -1
- package/man/mend.1 +1 -1
- package/package.json +3 -3
package/dist/main.js
CHANGED
|
@@ -1,9 +1,13 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import {
|
|
3
|
+
GATEWAY_STATUSES,
|
|
3
4
|
HARNESS_COMMANDS,
|
|
4
5
|
LIVE_STATUSES,
|
|
6
|
+
MendRequestError,
|
|
5
7
|
agentIsLive,
|
|
6
8
|
agentOutcome,
|
|
9
|
+
agentStartingFacts,
|
|
10
|
+
agentStartingLine,
|
|
7
11
|
captureLineOf,
|
|
8
12
|
claudeGrantFacts,
|
|
9
13
|
createSseParser,
|
|
@@ -18,6 +22,7 @@ import {
|
|
|
18
22
|
landingFactsFromWire,
|
|
19
23
|
matchProjectByCwd,
|
|
20
24
|
narrowCredential,
|
|
25
|
+
noAnswerError,
|
|
21
26
|
normalizeGitAuthor,
|
|
22
27
|
normalizeProjectName,
|
|
23
28
|
normalizeRemoteUrl,
|
|
@@ -27,8 +32,9 @@ import {
|
|
|
27
32
|
sameGrant,
|
|
28
33
|
servicesHoldOf,
|
|
29
34
|
sessionDisplayName,
|
|
35
|
+
spoken,
|
|
30
36
|
trackBracketedPaste
|
|
31
|
-
} from "./chunk-
|
|
37
|
+
} from "./chunk-5DORQXQT.js";
|
|
32
38
|
|
|
33
39
|
// src/main.ts
|
|
34
40
|
import { spawn as spawn3, spawnSync as spawnSync5 } from "node:child_process";
|
|
@@ -171,6 +177,45 @@ var startAgentShare = (options) => {
|
|
|
171
177
|
};
|
|
172
178
|
};
|
|
173
179
|
|
|
180
|
+
// src/attach-starting.ts
|
|
181
|
+
var startingLabelOf = (harness) => harness === "run" ? "the command" : harness === "shell" ? "the shell" : harness;
|
|
182
|
+
var CLEAR_LINE = "\r\x1B[2K";
|
|
183
|
+
var startingLine = (label, facts, elapsedMs, cancelHint) => `${CLEAR_LINE} ${agentStartingLine(label, facts, elapsedMs)}${cancelHint === "" ? "" : ` \xB7 ${cancelHint}`}`;
|
|
184
|
+
var firstOutputGate = (options) => {
|
|
185
|
+
const attachedAt = options.now();
|
|
186
|
+
let facts = { startedAt: null, freshMachine: false };
|
|
187
|
+
let shown = false;
|
|
188
|
+
let done = false;
|
|
189
|
+
let interval = null;
|
|
190
|
+
const draw = () => {
|
|
191
|
+
if (done) return;
|
|
192
|
+
shown = true;
|
|
193
|
+
const since = facts.startedAt ?? attachedAt;
|
|
194
|
+
options.write(startingLine(options.label, facts, options.now() - since, options.cancelHint));
|
|
195
|
+
};
|
|
196
|
+
const grace = setTimeout(() => {
|
|
197
|
+
draw();
|
|
198
|
+
interval = setInterval(draw, options.intervalMs ?? 1e3);
|
|
199
|
+
}, options.graceMs ?? 400);
|
|
200
|
+
const finish = () => {
|
|
201
|
+
if (done) return;
|
|
202
|
+
done = true;
|
|
203
|
+
clearTimeout(grace);
|
|
204
|
+
if (interval !== null) clearInterval(interval);
|
|
205
|
+
if (shown) options.write(CLEAR_LINE);
|
|
206
|
+
shown = false;
|
|
207
|
+
};
|
|
208
|
+
return {
|
|
209
|
+
pass: finish,
|
|
210
|
+
stop: finish,
|
|
211
|
+
update: (next) => {
|
|
212
|
+
facts = next;
|
|
213
|
+
if (shown) draw();
|
|
214
|
+
},
|
|
215
|
+
shown: () => shown
|
|
216
|
+
};
|
|
217
|
+
};
|
|
218
|
+
|
|
174
219
|
// src/claude-grant.ts
|
|
175
220
|
import { spawnSync } from "node:child_process";
|
|
176
221
|
import { createHash } from "node:crypto";
|
|
@@ -3655,6 +3700,78 @@ var pullCommand = async (api2, download2, args, cwd = process.cwd()) => {
|
|
|
3655
3700
|
for (const line of fetchedLines(fetched)) say(line);
|
|
3656
3701
|
};
|
|
3657
3702
|
|
|
3703
|
+
// src/launch-follow.ts
|
|
3704
|
+
var SETTLED = /* @__PURE__ */ new Set(["completed", "failed", "stopped", "stopping"]);
|
|
3705
|
+
var agentRuns = (agent) => agent !== null && agent.kind !== "agent-protocol" && agent.exitedAt === null && agent.status === "running" && agent.sealantSessionId !== null;
|
|
3706
|
+
var startingLineOf = (session) => {
|
|
3707
|
+
const summary = session.summary?.trim() ?? "";
|
|
3708
|
+
if (summary === "") return session.status;
|
|
3709
|
+
if (/^[a-z][a-z -]*·/u.test(summary)) return summary;
|
|
3710
|
+
return `${session.status} \xB7 ${summary}`;
|
|
3711
|
+
};
|
|
3712
|
+
var refusalOf = (error) => error instanceof MendRequestError && error.kind === "http" && (error.status === null || !GATEWAY_STATUSES.has(error.status)) ? error.message : null;
|
|
3713
|
+
var followStart = async (options) => {
|
|
3714
|
+
const pollMs = options.pollMs ?? 2e3;
|
|
3715
|
+
const unreachableAfterMs = options.unreachableAfterMs ?? 6e4;
|
|
3716
|
+
const launch2 = {
|
|
3717
|
+
state: options.start === null ? { kind: "no-answer" } : { kind: "pending" }
|
|
3718
|
+
};
|
|
3719
|
+
const start = options.start;
|
|
3720
|
+
const launched = (async () => {
|
|
3721
|
+
if (start === null) return;
|
|
3722
|
+
try {
|
|
3723
|
+
launch2.state = { kind: "answered", session: await start };
|
|
3724
|
+
} catch (error) {
|
|
3725
|
+
const refusal = refusalOf(error);
|
|
3726
|
+
launch2.state = refusal === null ? { kind: "no-answer" } : { kind: "refused", message: refusal };
|
|
3727
|
+
}
|
|
3728
|
+
})();
|
|
3729
|
+
let lastLine = null;
|
|
3730
|
+
let failingSince = null;
|
|
3731
|
+
let seenStarting = false;
|
|
3732
|
+
for (; ; ) {
|
|
3733
|
+
if (launch2.state.kind === "pending") await Promise.race([launched, options.sleep(pollMs)]);
|
|
3734
|
+
const state = launch2.state;
|
|
3735
|
+
if (state.kind === "refused") return { kind: "refused", message: state.message };
|
|
3736
|
+
if (state.kind === "answered" && state.session.status === "running") {
|
|
3737
|
+
return { kind: "live", session: state.session };
|
|
3738
|
+
}
|
|
3739
|
+
let detail;
|
|
3740
|
+
try {
|
|
3741
|
+
detail = await options.read();
|
|
3742
|
+
} catch (error) {
|
|
3743
|
+
const refusal = refusalOf(error);
|
|
3744
|
+
if (refusal !== null) return { kind: "refused", message: refusal };
|
|
3745
|
+
failingSince ??= options.now();
|
|
3746
|
+
if (state.kind !== "pending" && options.now() - failingSince >= unreachableAfterMs) {
|
|
3747
|
+
return {
|
|
3748
|
+
kind: "unreachable",
|
|
3749
|
+
message: error instanceof Error ? error.message : String(error)
|
|
3750
|
+
};
|
|
3751
|
+
}
|
|
3752
|
+
if (state.kind !== "pending") await options.sleep(pollMs);
|
|
3753
|
+
continue;
|
|
3754
|
+
}
|
|
3755
|
+
failingSince = null;
|
|
3756
|
+
const { session, currentAgent } = detail;
|
|
3757
|
+
if (agentRuns(currentAgent)) return { kind: "live", session };
|
|
3758
|
+
if (SETTLED.has(session.status)) {
|
|
3759
|
+
if (seenStarting || state.kind !== "pending") return { kind: "settled", session };
|
|
3760
|
+
} else {
|
|
3761
|
+
seenStarting = true;
|
|
3762
|
+
}
|
|
3763
|
+
if (currentAgent === null && state.kind === "answered" && state.session.status === "running" && session.status === "running") {
|
|
3764
|
+
return { kind: "live", session };
|
|
3765
|
+
}
|
|
3766
|
+
const line = startingLineOf(session);
|
|
3767
|
+
if (line !== lastLine) {
|
|
3768
|
+
lastLine = line;
|
|
3769
|
+
options.onLine(line);
|
|
3770
|
+
}
|
|
3771
|
+
if (state.kind !== "pending") await options.sleep(pollMs);
|
|
3772
|
+
}
|
|
3773
|
+
};
|
|
3774
|
+
|
|
3658
3775
|
// src/login.ts
|
|
3659
3776
|
import { spawn as spawn2 } from "node:child_process";
|
|
3660
3777
|
import * as os5 from "node:os";
|
|
@@ -7483,9 +7600,10 @@ var herdrAgentOf = (harness) => {
|
|
|
7483
7600
|
if (harness === "codex" || harness === "claude" || harness === "opencode") return harness;
|
|
7484
7601
|
return null;
|
|
7485
7602
|
};
|
|
7603
|
+
var noHerdrHint = () => void 0;
|
|
7486
7604
|
var hintHerdrAttachment = (harness) => {
|
|
7487
7605
|
const agent = herdrAgentOf(harness);
|
|
7488
|
-
if (process.env["HERDR_ENV"] !== "1" || agent === null) return
|
|
7606
|
+
if (process.env["HERDR_ENV"] !== "1" || agent === null) return noHerdrHint;
|
|
7489
7607
|
const carrier = spawn3(
|
|
7490
7608
|
process.execPath,
|
|
7491
7609
|
["-e", "process.stdin.resume();process.stdin.once('end',()=>process.exit(0))"],
|
|
@@ -7511,6 +7629,8 @@ var request = async (config, method, route, body) => {
|
|
|
7511
7629
|
const headers = { "content-type": "application/json" };
|
|
7512
7630
|
if (config.token !== null) headers["authorization"] = `Bearer ${config.token}`;
|
|
7513
7631
|
let response;
|
|
7632
|
+
const started = Date.now();
|
|
7633
|
+
const call = `${method} ${route}`;
|
|
7514
7634
|
try {
|
|
7515
7635
|
response = await fetch(`${config.url}/api${route}`, {
|
|
7516
7636
|
method,
|
|
@@ -7518,23 +7638,30 @@ var request = async (config, method, route, body) => {
|
|
|
7518
7638
|
// Spread, not `body: null` — fresh oxlint rejects a body key on GETs.
|
|
7519
7639
|
...body === void 0 ? {} : { body: JSON.stringify(body) }
|
|
7520
7640
|
});
|
|
7521
|
-
} catch {
|
|
7522
|
-
throw
|
|
7641
|
+
} catch (error) {
|
|
7642
|
+
throw noAnswerError(error, config.url, call, Date.now() - started);
|
|
7523
7643
|
}
|
|
7524
7644
|
if (response.status === 401) {
|
|
7525
|
-
throw new
|
|
7526
|
-
|
|
7645
|
+
throw new MendRequestError(
|
|
7646
|
+
"http",
|
|
7647
|
+
config.token === null ? `not signed in to ${config.url} \u2014 run: mend login` : `unauthorized at ${config.url} \u2014 the saved token was rejected; run: mend login`,
|
|
7648
|
+
401
|
|
7527
7649
|
);
|
|
7528
7650
|
}
|
|
7529
|
-
|
|
7651
|
+
let text;
|
|
7652
|
+
try {
|
|
7653
|
+
text = await response.text();
|
|
7654
|
+
} catch (error) {
|
|
7655
|
+
throw noAnswerError(error, config.url, call, Date.now() - started);
|
|
7656
|
+
}
|
|
7530
7657
|
if (!response.ok) {
|
|
7531
|
-
let message = `${
|
|
7658
|
+
let message = `${call} \u2192 ${response.status}`;
|
|
7532
7659
|
try {
|
|
7533
7660
|
const parsed = JSON.parse(text);
|
|
7534
7661
|
message = parsed.message ?? message;
|
|
7535
7662
|
} catch {
|
|
7536
7663
|
}
|
|
7537
|
-
throw new
|
|
7664
|
+
throw new MendRequestError("http", message, response.status);
|
|
7538
7665
|
}
|
|
7539
7666
|
try {
|
|
7540
7667
|
return JSON.parse(text);
|
|
@@ -7553,10 +7680,11 @@ var download = (config) => async (route) => {
|
|
|
7553
7680
|
const headers = {};
|
|
7554
7681
|
if (config.token !== null) headers["authorization"] = `Bearer ${config.token}`;
|
|
7555
7682
|
let response;
|
|
7683
|
+
const started = Date.now();
|
|
7556
7684
|
try {
|
|
7557
7685
|
response = await fetch(`${config.url}/api${route}`, { headers });
|
|
7558
|
-
} catch {
|
|
7559
|
-
return fail6(
|
|
7686
|
+
} catch (error) {
|
|
7687
|
+
return fail6(noAnswerError(error, config.url, `GET ${route}`, Date.now() - started).message);
|
|
7560
7688
|
}
|
|
7561
7689
|
if (response.status === 401) {
|
|
7562
7690
|
return fail6(
|
|
@@ -7567,13 +7695,14 @@ var download = (config) => async (route) => {
|
|
|
7567
7695
|
return { status: response.status, header: (name) => response.headers.get(name), bytes };
|
|
7568
7696
|
};
|
|
7569
7697
|
var warnedLegacyBearer = false;
|
|
7570
|
-
var mintTicket = (config) => async (target, params) => {
|
|
7698
|
+
var mintTicket = (config, signal) => async (target, params) => {
|
|
7571
7699
|
const headers = { "content-type": "application/json" };
|
|
7572
7700
|
if (config.token !== null) headers["authorization"] = `Bearer ${config.token}`;
|
|
7573
7701
|
const response = await fetch(`${config.url}/api/upgrade-tickets`, {
|
|
7574
7702
|
method: "POST",
|
|
7575
7703
|
headers,
|
|
7576
|
-
body: JSON.stringify({ target, ...params })
|
|
7704
|
+
body: JSON.stringify({ target, ...params }),
|
|
7705
|
+
...signal === void 0 ? {} : { signal }
|
|
7577
7706
|
});
|
|
7578
7707
|
if (response.status === 404) {
|
|
7579
7708
|
if (await mintRefusedInTransit(config.url)) throw new Error(MINT_REFUSED_IN_TRANSIT);
|
|
@@ -7591,12 +7720,12 @@ var mintTicket = (config) => async (target, params) => {
|
|
|
7591
7720
|
throw new Error("upgrade ticket missing from the answer");
|
|
7592
7721
|
return { kind: "ticket", ticket: minted.ticket };
|
|
7593
7722
|
};
|
|
7594
|
-
var socketUrl = (config, target, params, extra) => upgradeUrl({
|
|
7723
|
+
var socketUrl = (config, target, params, extra, signal) => upgradeUrl({
|
|
7595
7724
|
serverUrl: parseMendUrl(config.url).toString(),
|
|
7596
7725
|
target,
|
|
7597
7726
|
params,
|
|
7598
7727
|
...extra === void 0 ? {} : { extra },
|
|
7599
|
-
mint: mintTicket(config),
|
|
7728
|
+
mint: mintTicket(config, signal),
|
|
7600
7729
|
legacyToken: config.token
|
|
7601
7730
|
});
|
|
7602
7731
|
var boundApi = (config) => (method, route, body) => api(config, method, route, body);
|
|
@@ -7607,7 +7736,10 @@ var withSpinner = async (label, work) => {
|
|
|
7607
7736
|
let frame = 0;
|
|
7608
7737
|
const timer = setInterval(() => {
|
|
7609
7738
|
const seconds2 = Math.round((Date.now() - started) / 1e3);
|
|
7610
|
-
|
|
7739
|
+
const text = typeof label === "string" ? label : label();
|
|
7740
|
+
process.stdout.write(
|
|
7741
|
+
`\r\x1B[2K ${frames[frame % frames.length]} ${text} ${dim6(`${seconds2}s`)} `
|
|
7742
|
+
);
|
|
7611
7743
|
frame += 1;
|
|
7612
7744
|
}, 120);
|
|
7613
7745
|
try {
|
|
@@ -7617,6 +7749,52 @@ var withSpinner = async (label, work) => {
|
|
|
7617
7749
|
process.stdout.write("\r\x1B[2K");
|
|
7618
7750
|
}
|
|
7619
7751
|
};
|
|
7752
|
+
var followStarting = async (config, sessionId, label, start) => {
|
|
7753
|
+
let line = label;
|
|
7754
|
+
const timers = /* @__PURE__ */ new Set();
|
|
7755
|
+
const sleep3 = (ms) => new Promise((resolve7) => {
|
|
7756
|
+
const timer = setTimeout(() => {
|
|
7757
|
+
timers.delete(timer);
|
|
7758
|
+
resolve7();
|
|
7759
|
+
}, ms);
|
|
7760
|
+
timers.add(timer);
|
|
7761
|
+
});
|
|
7762
|
+
try {
|
|
7763
|
+
return await withSpinner(
|
|
7764
|
+
() => line,
|
|
7765
|
+
followStart({
|
|
7766
|
+
start,
|
|
7767
|
+
read: () => request(config, "GET", `/sessions/${sessionId}`),
|
|
7768
|
+
onLine: (next) => {
|
|
7769
|
+
line = next === "starting" ? label : next;
|
|
7770
|
+
if (process.stdout.isTTY !== true) say6(dim6(` ${next}`));
|
|
7771
|
+
},
|
|
7772
|
+
sleep: sleep3,
|
|
7773
|
+
now: Date.now
|
|
7774
|
+
})
|
|
7775
|
+
);
|
|
7776
|
+
} finally {
|
|
7777
|
+
for (const timer of timers) clearTimeout(timer);
|
|
7778
|
+
}
|
|
7779
|
+
};
|
|
7780
|
+
var startedOrExit = (config, sessionId, outcome) => {
|
|
7781
|
+
const id8 = sessionId.slice(0, 8);
|
|
7782
|
+
switch (outcome.kind) {
|
|
7783
|
+
case "live":
|
|
7784
|
+
return outcome.session;
|
|
7785
|
+
case "refused":
|
|
7786
|
+
return fail6(outcome.message);
|
|
7787
|
+
case "settled":
|
|
7788
|
+
return fail6(
|
|
7789
|
+
`session ${id8} \xB7 ${startingLineOf(outcome.session)} before its agent ran \xB7 ${config.url}/sessions/${sessionId}`
|
|
7790
|
+
);
|
|
7791
|
+
case "unreachable":
|
|
7792
|
+
return fail6(
|
|
7793
|
+
`${outcome.message} \xB7 the session may still be starting \xB7 follow it: mend attach ${id8}`
|
|
7794
|
+
);
|
|
7795
|
+
}
|
|
7796
|
+
};
|
|
7797
|
+
var startAndFollow = async (config, sessionId, label, start) => startedOrExit(config, sessionId, await followStarting(config, sessionId, label, start));
|
|
7620
7798
|
var adopt = async (config, args) => {
|
|
7621
7799
|
const nameFlag = args.indexOf("--name");
|
|
7622
7800
|
const name = nameFlag !== -1 && args[nameFlag + 1] !== void 0 ? String(args[nameFlag + 1]) : null;
|
|
@@ -7818,9 +7996,11 @@ var launch = async (config, harness, args) => {
|
|
|
7818
7996
|
...parsed.ask ? { permissionMode: "ask" } : {},
|
|
7819
7997
|
...parsed.fast ? { speed: "fast" } : {}
|
|
7820
7998
|
} : { argv };
|
|
7821
|
-
await
|
|
7999
|
+
await startAndFollow(
|
|
8000
|
+
config,
|
|
8001
|
+
session.id,
|
|
7822
8002
|
"provisioning workspace \u2014 a first launch builds the harness image (can take minutes)\u2026",
|
|
7823
|
-
|
|
8003
|
+
request(config, "POST", `/sessions/${session.id}/launch`, launchBody)
|
|
7824
8004
|
);
|
|
7825
8005
|
if (lifecycle === "detach") {
|
|
7826
8006
|
say6(`${green6("\u2713 recording")} \xB7 running detached`);
|
|
@@ -7842,62 +8022,74 @@ var autoLandLine = (override, project2) => {
|
|
|
7842
8022
|
}
|
|
7843
8023
|
return `${green6("\u2713")} automatic landing \xB7 ${override ? "on" : "off"} for this session ${dim6("\xB7 from this terminal, land with mend land")}`;
|
|
7844
8024
|
};
|
|
7845
|
-
var
|
|
7846
|
-
|
|
7847
|
-
|
|
7848
|
-
|
|
7849
|
-
|
|
7850
|
-
|
|
7851
|
-
|
|
7852
|
-
|
|
8025
|
+
var ATTACH_CONNECT_TIMEOUT_MS = (() => {
|
|
8026
|
+
const configured = Number(process.env["MEND_ATTACH_TIMEOUT_MS"]);
|
|
8027
|
+
return Number.isFinite(configured) && configured > 0 ? configured : 3e4;
|
|
8028
|
+
})();
|
|
8029
|
+
var connectingLine = (sessionId, elapsedMs) => `\r\x1B[2K connecting to ${sessionId.slice(0, 8)} \xB7 ${Math.round(elapsedMs / 1e3)}s \xB7 ${detachKeyEnabled ? "Ctrl+] or " : ""}Ctrl+C cancels`;
|
|
8030
|
+
var takeTerminal = () => {
|
|
8031
|
+
if (process.stdin.isTTY !== true) {
|
|
8032
|
+
console.error(
|
|
8033
|
+
"mend: stdin is not a terminal, so it cannot be put in raw mode \u2014 keys reach the session a line at a time and echo here"
|
|
7853
8034
|
);
|
|
7854
|
-
|
|
7855
|
-
return "unavailable";
|
|
8035
|
+
return { raw: false };
|
|
7856
8036
|
}
|
|
7857
|
-
const ws = new WebSocket(url);
|
|
7858
|
-
ws.binaryType = "arraybuffer";
|
|
7859
8037
|
try {
|
|
7860
|
-
|
|
7861
|
-
|
|
7862
|
-
|
|
7863
|
-
|
|
7864
|
-
|
|
7865
|
-
return
|
|
8038
|
+
process.stdin.setRawMode(true);
|
|
8039
|
+
} catch (error) {
|
|
8040
|
+
console.error(
|
|
8041
|
+
`mend: could not put this terminal in raw mode (${error instanceof Error ? error.message : String(error)}) \u2014 keys reach the session a line at a time and echo here`
|
|
8042
|
+
);
|
|
8043
|
+
return { raw: false };
|
|
7866
8044
|
}
|
|
7867
|
-
|
|
7868
|
-
|
|
7869
|
-
|
|
7870
|
-
finishAttachment = resolve7;
|
|
7871
|
-
});
|
|
7872
|
-
const onClose = () => finishAttachment?.();
|
|
7873
|
-
if (ws.readyState === WebSocket.CLOSED) finishAttachment?.();
|
|
7874
|
-
else ws.addEventListener("close", onClose, { once: true });
|
|
7875
|
-
const sendResize = () => {
|
|
7876
|
-
if (ws.readyState !== WebSocket.OPEN) return;
|
|
7877
|
-
ws.send(
|
|
7878
|
-
JSON.stringify({
|
|
7879
|
-
t: "resize",
|
|
7880
|
-
cols: process.stdout.columns ?? 80,
|
|
7881
|
-
rows: process.stdout.rows ?? 24
|
|
7882
|
-
})
|
|
8045
|
+
if (!process.stdin.isRaw) {
|
|
8046
|
+
console.error(
|
|
8047
|
+
"mend: this terminal did not accept raw mode \u2014 keys reach the session a line at a time and echo here"
|
|
7883
8048
|
);
|
|
8049
|
+
return { raw: false };
|
|
8050
|
+
}
|
|
8051
|
+
return { raw: true };
|
|
8052
|
+
};
|
|
8053
|
+
var terminalSize = () => ({
|
|
8054
|
+
cols: process.stdout.columns || 80,
|
|
8055
|
+
rows: process.stdout.rows || 24
|
|
8056
|
+
});
|
|
8057
|
+
var attachTty = async (config, sessionId, harness, from, processId, options) => {
|
|
8058
|
+
const interactive = options?.readOnly !== true;
|
|
8059
|
+
const rawModeEnabled = interactive ? takeTerminal().raw : false;
|
|
8060
|
+
const connecting = new AbortController();
|
|
8061
|
+
let abortReason = null;
|
|
8062
|
+
const abort = (reason) => {
|
|
8063
|
+
abortReason ??= reason;
|
|
8064
|
+
connecting.abort();
|
|
7884
8065
|
};
|
|
7885
|
-
|
|
7886
|
-
const rawTty = process.stdin.isTTY === true;
|
|
7887
|
-
let rawModeEnabled = false;
|
|
8066
|
+
let ws = null;
|
|
7888
8067
|
let detached = false;
|
|
7889
8068
|
let sawEnd = false;
|
|
7890
8069
|
let interrupted = false;
|
|
8070
|
+
let finishAttachment;
|
|
8071
|
+
const finished = new Promise((resolve7) => {
|
|
8072
|
+
finishAttachment = resolve7;
|
|
8073
|
+
});
|
|
7891
8074
|
const onSignal = () => {
|
|
7892
8075
|
interrupted = true;
|
|
7893
|
-
|
|
8076
|
+
abort("interrupted");
|
|
8077
|
+
ws?.close();
|
|
7894
8078
|
finishAttachment?.();
|
|
7895
8079
|
};
|
|
7896
8080
|
const signals = ["SIGHUP", "SIGINT", "SIGTERM"];
|
|
8081
|
+
const sendResize = (size = terminalSize()) => {
|
|
8082
|
+
if (ws === null || ws.readyState !== WebSocket.OPEN) return;
|
|
8083
|
+
ws.send(JSON.stringify({ t: "resize", cols: size.cols, rows: size.rows }));
|
|
8084
|
+
};
|
|
8085
|
+
const onWinch = () => sendResize();
|
|
7897
8086
|
let bracketedPaste = false;
|
|
8087
|
+
let starting = null;
|
|
7898
8088
|
const onTtyFrame = (event) => {
|
|
7899
8089
|
if (typeof event.data !== "string") {
|
|
7900
8090
|
const bytes = Buffer.from(event.data);
|
|
8091
|
+
if (bytes.length === 0) return;
|
|
8092
|
+
starting?.pass();
|
|
7901
8093
|
bracketedPaste = trackBracketedPaste(bytes, bracketedPaste);
|
|
7902
8094
|
process.stdout.write(bytes);
|
|
7903
8095
|
return;
|
|
@@ -7908,15 +8100,24 @@ var attachTty = async (config, sessionId, harness, from, processId, options) =>
|
|
|
7908
8100
|
return;
|
|
7909
8101
|
}
|
|
7910
8102
|
sawEnd = true;
|
|
7911
|
-
ws
|
|
8103
|
+
ws?.close();
|
|
7912
8104
|
finishAttachment?.();
|
|
7913
8105
|
} catch {
|
|
7914
8106
|
}
|
|
7915
8107
|
};
|
|
8108
|
+
const forward = (bytes) => {
|
|
8109
|
+
if (ws !== null && ws.readyState === WebSocket.OPEN) ws.send(new Uint8Array(bytes).buffer);
|
|
8110
|
+
};
|
|
7916
8111
|
const onKeys = (data) => {
|
|
8112
|
+
if (ws === null) {
|
|
8113
|
+
if (detachKeyEnabled && isDetachChunk(data)) abort("detached");
|
|
8114
|
+
else if (data.includes(3)) abort("interrupted");
|
|
8115
|
+
return;
|
|
8116
|
+
}
|
|
7917
8117
|
if (detachKeyEnabled && isDetachChunk(data)) {
|
|
7918
8118
|
detached = true;
|
|
7919
8119
|
ws.close();
|
|
8120
|
+
finishAttachment?.();
|
|
7920
8121
|
return;
|
|
7921
8122
|
}
|
|
7922
8123
|
if (ws.readyState !== WebSocket.OPEN) return;
|
|
@@ -7926,9 +8127,6 @@ var attachTty = async (config, sessionId, harness, from, processId, options) =>
|
|
|
7926
8127
|
}
|
|
7927
8128
|
ws.send(new Uint8Array(data).buffer);
|
|
7928
8129
|
};
|
|
7929
|
-
const forward = (bytes) => {
|
|
7930
|
-
if (ws.readyState === WebSocket.OPEN) ws.send(new Uint8Array(bytes).buffer);
|
|
7931
|
-
};
|
|
7932
8130
|
const pasteClipboardImage = async (keystroke) => {
|
|
7933
8131
|
const image = await readClipboardImage();
|
|
7934
8132
|
if (image === null) {
|
|
@@ -7947,36 +8145,102 @@ var attachTty = async (config, sessionId, harness, from, processId, options) =>
|
|
|
7947
8145
|
process.stdout.write("\x07");
|
|
7948
8146
|
}
|
|
7949
8147
|
};
|
|
8148
|
+
const connect2 = async () => {
|
|
8149
|
+
let url;
|
|
8150
|
+
try {
|
|
8151
|
+
url = await socketUrl(
|
|
8152
|
+
config,
|
|
8153
|
+
"tty",
|
|
8154
|
+
processId === void 0 ? { session: sessionId } : { process: processId },
|
|
8155
|
+
{ from: from.toString() },
|
|
8156
|
+
connecting.signal
|
|
8157
|
+
);
|
|
8158
|
+
} catch {
|
|
8159
|
+
return connecting.signal.aborted ? "aborted" : "unavailable";
|
|
8160
|
+
}
|
|
8161
|
+
if (connecting.signal.aborted) return "aborted";
|
|
8162
|
+
const socket = new WebSocket(url);
|
|
8163
|
+
socket.binaryType = "arraybuffer";
|
|
8164
|
+
const opened = await new Promise((resolve7) => {
|
|
8165
|
+
const onAbort = () => resolve7(false);
|
|
8166
|
+
connecting.signal.addEventListener("abort", onAbort, { once: true });
|
|
8167
|
+
const settle = (value) => {
|
|
8168
|
+
connecting.signal.removeEventListener("abort", onAbort);
|
|
8169
|
+
resolve7(value);
|
|
8170
|
+
};
|
|
8171
|
+
socket.addEventListener("open", () => settle(true), { once: true });
|
|
8172
|
+
socket.addEventListener("error", () => settle(false), { once: true });
|
|
8173
|
+
});
|
|
8174
|
+
if (opened && !connecting.signal.aborted) return socket;
|
|
8175
|
+
socket.close();
|
|
8176
|
+
return connecting.signal.aborted ? "aborted" : "unavailable";
|
|
8177
|
+
};
|
|
8178
|
+
const started = Date.now();
|
|
8179
|
+
let connectingShown = false;
|
|
8180
|
+
const connectingTimer = setInterval(() => {
|
|
8181
|
+
if (process.stdout.isTTY !== true) return;
|
|
8182
|
+
connectingShown = true;
|
|
8183
|
+
process.stdout.write(connectingLine(sessionId, Date.now() - started));
|
|
8184
|
+
}, 1e3);
|
|
8185
|
+
const deadline = setTimeout(() => abort("no-answer"), ATTACH_CONNECT_TIMEOUT_MS);
|
|
8186
|
+
const stopConnecting = () => {
|
|
8187
|
+
for (const timer of [connectingTimer, deadline]) clearTimeout(timer);
|
|
8188
|
+
if (connectingShown) process.stdout.write("\r\x1B[2K");
|
|
8189
|
+
connectingShown = false;
|
|
8190
|
+
};
|
|
8191
|
+
let stopHerdrHint = noHerdrHint;
|
|
7950
8192
|
try {
|
|
7951
|
-
sendResize();
|
|
7952
|
-
process.stdout.on("resize", onWinch);
|
|
7953
8193
|
if (options?.handleSignals === true) for (const signal of signals) process.on(signal, onSignal);
|
|
7954
|
-
if (
|
|
7955
|
-
if (rawTty) {
|
|
7956
|
-
process.stdin.setRawMode(true);
|
|
7957
|
-
rawModeEnabled = true;
|
|
7958
|
-
}
|
|
7959
|
-
process.stdin.resume();
|
|
8194
|
+
if (interactive) {
|
|
7960
8195
|
process.stdin.on("data", onKeys);
|
|
8196
|
+
process.stdin.resume();
|
|
7961
8197
|
}
|
|
8198
|
+
const connected = await connect2();
|
|
8199
|
+
stopConnecting();
|
|
8200
|
+
if (connected === "unavailable") return "unavailable";
|
|
8201
|
+
if (connected === "aborted") return abortReason ?? "interrupted";
|
|
8202
|
+
ws = connected;
|
|
8203
|
+
if (interactive && processId === void 0 && process.stdout.isTTY === true) {
|
|
8204
|
+
const gate = firstOutputGate({
|
|
8205
|
+
label: startingLabelOf(harness),
|
|
8206
|
+
write: (text) => process.stdout.write(text),
|
|
8207
|
+
now: Date.now,
|
|
8208
|
+
cancelHint: detachKeyEnabled ? "Ctrl+] detaches" : ""
|
|
8209
|
+
});
|
|
8210
|
+
starting = gate;
|
|
8211
|
+
void request(config, "GET", `/sessions/${sessionId}`).then(
|
|
8212
|
+
(detail) => gate.update(agentStartingFacts(detail.currentAgent, detail.processes, Date.now())),
|
|
8213
|
+
() => void 0
|
|
8214
|
+
);
|
|
8215
|
+
}
|
|
8216
|
+
stopHerdrHint = hintHerdrAttachment(harness);
|
|
8217
|
+
const onClose = () => finishAttachment?.();
|
|
8218
|
+
if (ws.readyState === WebSocket.CLOSED) finishAttachment?.();
|
|
8219
|
+
else ws.addEventListener("close", onClose, { once: true });
|
|
7962
8220
|
ws.addEventListener("message", onTtyFrame);
|
|
8221
|
+
const size = terminalSize();
|
|
8222
|
+
if (size.rows > 1) sendResize({ cols: size.cols, rows: size.rows - 1 });
|
|
8223
|
+
setTimeout(() => sendResize(), 80);
|
|
8224
|
+
process.stdout.on("resize", onWinch);
|
|
7963
8225
|
await finished;
|
|
7964
8226
|
return detached ? "detached" : interrupted ? "interrupted" : sawEnd ? "ended" : "dropped";
|
|
7965
8227
|
} finally {
|
|
8228
|
+
stopConnecting();
|
|
8229
|
+
starting?.stop();
|
|
7966
8230
|
for (const signal of signals) process.off(signal, onSignal);
|
|
7967
8231
|
process.stdin.off("data", onKeys);
|
|
7968
8232
|
process.stdout.off("resize", onWinch);
|
|
7969
|
-
ws
|
|
7970
|
-
ws.removeEventListener("close", onClose);
|
|
8233
|
+
ws?.removeEventListener("message", onTtyFrame);
|
|
7971
8234
|
if (rawModeEnabled) process.stdin.setRawMode(false);
|
|
7972
|
-
if (
|
|
8235
|
+
if (interactive && ws !== null && process.stdout.isTTY === true) {
|
|
7973
8236
|
process.stdout.write(
|
|
7974
8237
|
"\x1B[<u\x1B[=0;1u\x1B[?2004l\x1B[?1000l\x1B[?1002l\x1B[?1003l\x1B[?1006l\x1B[?1049l\x1B[?25h"
|
|
7975
8238
|
// show the cursor
|
|
7976
8239
|
);
|
|
7977
8240
|
}
|
|
7978
8241
|
process.stdin.pause();
|
|
7979
|
-
if (ws.readyState === WebSocket.OPEN || ws.readyState === WebSocket.CONNECTING)
|
|
8242
|
+
if (ws !== null && (ws.readyState === WebSocket.OPEN || ws.readyState === WebSocket.CONNECTING))
|
|
8243
|
+
ws.close();
|
|
7980
8244
|
stopHerdrHint();
|
|
7981
8245
|
}
|
|
7982
8246
|
};
|
|
@@ -8004,11 +8268,18 @@ var stopAndExit = async (config, sessionId) => {
|
|
|
8004
8268
|
say6(`${amber2("could not stop")} \u2014 the session may still run \xB7 mend stop ${id8}`);
|
|
8005
8269
|
process.exit(1);
|
|
8006
8270
|
};
|
|
8271
|
+
var noAnswerLine = (config, id8) => `${amber2("no answer")} \u2014 ${config.url} did not open the terminal within ${spoken(ATTACH_CONNECT_TIMEOUT_MS)}; the session keeps running \xB7 try again: mend attach ${id8} \xB7 check the server: mend doctor`;
|
|
8007
8272
|
var finishAttach = async (config, sessionId, outcome, mode = "background") => {
|
|
8008
8273
|
const id8 = sessionId.slice(0, 8);
|
|
8009
8274
|
if (outcome === "unavailable") {
|
|
8010
8275
|
return fail6(`tty attach unavailable: could not connect to ${config.url}`);
|
|
8011
8276
|
}
|
|
8277
|
+
if (outcome === "no-answer") {
|
|
8278
|
+
say6("");
|
|
8279
|
+
say6(noAnswerLine(config, id8));
|
|
8280
|
+
if (mode === "foreground") return stopAndExit(config, sessionId);
|
|
8281
|
+
process.exit(1);
|
|
8282
|
+
}
|
|
8012
8283
|
if (outcome === "detached") {
|
|
8013
8284
|
say6("");
|
|
8014
8285
|
say6(`${amber2("detached")} \u2014 the session keeps running; reattach: mend attach ${id8}`);
|
|
@@ -8070,6 +8341,9 @@ var attachPicked = async (config, session, tunnels) => {
|
|
|
8070
8341
|
say6(
|
|
8071
8342
|
`${green6("\u2713")} attaching to ${sessionDisplayName(session)} \xB7 ${session.harness} ${dim6(session.id.slice(0, 8))}${detachHint()}`
|
|
8072
8343
|
);
|
|
8344
|
+
if (session.status === "starting") {
|
|
8345
|
+
await startAndFollow(config, session.id, "starting \u2014 attaching once the agent runs\u2026", null);
|
|
8346
|
+
}
|
|
8073
8347
|
await tunnels?.start(session.id);
|
|
8074
8348
|
say6("");
|
|
8075
8349
|
const outcome = await attachWithTunnels(
|
|
@@ -8082,9 +8356,11 @@ var attachPicked = async (config, session, tunnels) => {
|
|
|
8082
8356
|
const detail = await api(config, "GET", `/sessions/${session.id}`);
|
|
8083
8357
|
if (detail.currentAgent?.kind === "agent-protocol" && agentIsLive(detail.session, detail.currentAgent)) {
|
|
8084
8358
|
say6(`${amber2("taking over")} from the protocol session`);
|
|
8085
|
-
await
|
|
8359
|
+
await startAndFollow(
|
|
8360
|
+
config,
|
|
8361
|
+
session.id,
|
|
8086
8362
|
"reopening as a terminal \u2014 same conversation\u2026",
|
|
8087
|
-
|
|
8363
|
+
request(config, "POST", `/sessions/${session.id}/handoff`, { to: "pty" })
|
|
8088
8364
|
);
|
|
8089
8365
|
say6("");
|
|
8090
8366
|
await attachOrExit(config, session.id, session.harness, "background", tunnels);
|
|
@@ -8240,6 +8516,10 @@ var shellCommand = async (config, args) => {
|
|
|
8240
8516
|
return fail6(`tty attach unavailable: could not connect to ${config.url}`);
|
|
8241
8517
|
}
|
|
8242
8518
|
say6("");
|
|
8519
|
+
if (outcome === "no-answer") {
|
|
8520
|
+
say6(noAnswerLine(config, session.id.slice(0, 8)));
|
|
8521
|
+
process.exit(1);
|
|
8522
|
+
}
|
|
8243
8523
|
if (outcome === "detached" || outcome === "interrupted" || outcome === "dropped") {
|
|
8244
8524
|
say6(`${amber2("detached")} \u2014 the shell keeps running and holds the workspace open`);
|
|
8245
8525
|
process.exit(0);
|
|
@@ -9627,9 +9907,11 @@ var completionsCommand = (args) => {
|
|
|
9627
9907
|
}
|
|
9628
9908
|
};
|
|
9629
9909
|
var supervisedRun = async (config, session, argv) => {
|
|
9630
|
-
const launched = await
|
|
9910
|
+
const launched = await startAndFollow(
|
|
9911
|
+
config,
|
|
9912
|
+
session.id,
|
|
9631
9913
|
"provisioning workspace \u2014 a first launch builds the harness image (can take minutes)\u2026",
|
|
9632
|
-
|
|
9914
|
+
request(config, "POST", `/sessions/${session.id}/launch`, { argv })
|
|
9633
9915
|
);
|
|
9634
9916
|
say6(
|
|
9635
9917
|
`${green6("\u2713 recording")} \xB7 run ${dim6(launched.id.slice(0, 8))} \xB7 workspace mounts the worktree`
|
|
@@ -9765,9 +10047,13 @@ var resumeCommand = async (config, args) => {
|
|
|
9765
10047
|
say6(`${cobalt2(" watch")} \xB7 ${config.url}/sessions/${match.id}`);
|
|
9766
10048
|
const priorAgent = detail.annotations.find((annotation) => annotation.sessionId === match.id)?.currentAgent ?? null;
|
|
9767
10049
|
const protocolPrior = priorAgent?.kind === "agent-protocol" && withHarness === null;
|
|
9768
|
-
await
|
|
10050
|
+
await startAndFollow(
|
|
10051
|
+
config,
|
|
10052
|
+
match.id,
|
|
9769
10053
|
protocolPrior ? "reopening as a terminal \u2014 same conversation\u2026" : "resuming \u2014 a fresh workspace restores the saved session state\u2026",
|
|
9770
|
-
protocolPrior ?
|
|
10054
|
+
protocolPrior ? request(config, "POST", `/sessions/${match.id}/handoff`, { to: "pty" }) : request(config, "POST", `/sessions/${match.id}/resume`, {
|
|
10055
|
+
harness: withHarness
|
|
10056
|
+
})
|
|
9771
10057
|
);
|
|
9772
10058
|
say6(`${green6("\u2713 recording")} \xB7 same worktree, conversation restored${detachHint()}`);
|
|
9773
10059
|
say6("");
|
|
@@ -9775,17 +10061,18 @@ var resumeCommand = async (config, args) => {
|
|
|
9775
10061
|
exitAfterSessionEnd(config, match.id);
|
|
9776
10062
|
};
|
|
9777
10063
|
var resumeForRejoin = async (config, sessionId, label) => {
|
|
9778
|
-
|
|
9779
|
-
|
|
9780
|
-
|
|
9781
|
-
|
|
9782
|
-
)
|
|
9783
|
-
|
|
9784
|
-
|
|
10064
|
+
const outcome = await followStarting(
|
|
10065
|
+
config,
|
|
10066
|
+
sessionId,
|
|
10067
|
+
label,
|
|
10068
|
+
request(config, "POST", `/sessions/${sessionId}/resume`, { harness: null })
|
|
10069
|
+
);
|
|
10070
|
+
if (outcome.kind === "refused") {
|
|
9785
10071
|
const refreshed = await api(config, "GET", `/sessions/${sessionId}`);
|
|
9786
10072
|
if (agentIsLive(refreshed.session, refreshed.currentAgent)) return false;
|
|
9787
|
-
return fail6(error instanceof Error ? error.message : String(error));
|
|
9788
10073
|
}
|
|
10074
|
+
startedOrExit(config, sessionId, outcome);
|
|
10075
|
+
return true;
|
|
9789
10076
|
};
|
|
9790
10077
|
var rejoinCommand = async (config, args) => {
|
|
9791
10078
|
const harnessFlag = args.indexOf("--harness");
|
|
@@ -9816,9 +10103,11 @@ var rejoinCommand = async (config, args) => {
|
|
|
9816
10103
|
let restored = false;
|
|
9817
10104
|
const currentAgent = detail.annotations.find((annotation) => annotation.sessionId === session.id)?.currentAgent ?? null;
|
|
9818
10105
|
if (currentAgent?.kind === "agent-protocol") {
|
|
9819
|
-
await
|
|
10106
|
+
await startAndFollow(
|
|
10107
|
+
config,
|
|
10108
|
+
session.id,
|
|
9820
10109
|
alreadyLive ? "taking over from the protocol session \u2014 same conversation\u2026" : "reopening as a terminal \u2014 same conversation\u2026",
|
|
9821
|
-
|
|
10110
|
+
request(config, "POST", `/sessions/${session.id}/handoff`, { to: "pty" })
|
|
9822
10111
|
);
|
|
9823
10112
|
restored = true;
|
|
9824
10113
|
} else if (!alreadyLive) {
|
|
@@ -10149,7 +10438,7 @@ var dashboard = async (config, options = {}) => {
|
|
|
10149
10438
|
);
|
|
10150
10439
|
process.exit(rerun.status ?? 0);
|
|
10151
10440
|
}
|
|
10152
|
-
const { runDashboard } = await import("./dashboard-
|
|
10441
|
+
const { runDashboard } = await import("./dashboard-KZ4XGZ6W.js");
|
|
10153
10442
|
const agentShare = await startShareIfBridge(config);
|
|
10154
10443
|
const tunnels = options.noTunnel === true || serverIsLocal(config) ? null : createServiceTunnels({
|
|
10155
10444
|
listServices: () => fetchServices(config),
|