skydive-cli 0.5.0-beta.44 → 0.5.0-beta.48
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/js/bin.mjs +19 -19
- package/dist/js/{boot-ClKjP9Mn.mjs → boot-BoqVFa4M.mjs} +19 -8
- package/dist/js/{client-BnpgrtCC.mjs → client-C2lPer4b.mjs} +48 -3
- package/dist/js/client-CSKr4Swa.mjs +5 -0
- package/dist/js/daemon-B57zYVAN.mjs +7 -0
- package/dist/js/{daemon-w8vtZ81t.mjs → daemon-B7Wv4P6W.mjs} +94 -5
- package/dist/js/{daemon-client-KKNss0NK.mjs → daemon-client-Btr9rmKl.mjs} +1 -1
- package/dist/js/daemon-client-DL3eNr6N.mjs +8 -0
- package/dist/js/{install-DrtNldUN.mjs → install-BOtarTEl.mjs} +2 -2
- package/dist/js/{print-Ds16-Q4O.mjs → print-SD_y9CTF.mjs} +1 -1
- package/dist/js/{print-Dc-xRapT.mjs → print-ejS3eAZz.mjs} +2 -2
- package/dist/js/{print-share-Df6eAG12.mjs → print-share-D20YvVbb.mjs} +1 -1
- package/dist/js/{rest-DpsJUYTL.mjs → rest-B-dizrBw.mjs} +1 -1
- package/dist/js/{rest-Do3mPVpC.mjs → rest-Cz-aN-ME.mjs} +1 -1
- package/package.json +5 -5
- package/dist/js/client-Cga4PJUc.mjs +0 -5
- package/dist/js/daemon-client-D5YCk5Hc.mjs +0 -8
- package/dist/js/daemon-mLypjXdu.mjs +0 -7
package/dist/js/bin.mjs
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { o as name, s as version, t as createRestClient } from "./rest-
|
|
3
|
-
import { B as resolveSession$1, C as deleteConfig, D as getPreference, E as getLastSeenVersion, G as setLastSeenVersion, H as saveConfig, I as resolveAppUrl, L as resolveChatAuth, M as getStoredApiKeyId, N as getStoredApiKeyWorkspaceName, O as getPromptHistoryPath, P as getUpdateCheckDisabled, R as resolveConfig, S as PREFERENCES, T as getDefaultAgent, U as saveSession, _ as API_KEYS_URL, b as DEFAULT_API_URL, i as resolveAgent$1, j as getShareMachineDefault, v as API_KEY_FAMILY_PREFIX, w as getConfigPath, y as API_KEY_PREFIX, z as resolveManagementAuth } from "./print-
|
|
4
|
-
import { A as installAgentAlias, B as resolveWorkspaceId, D as machineOsFromPlatform, E as buildImportSeedPrompt, F as detectShell, I as ensureActiveOrganization, L as getActiveWorkspaceId, M as takenAliasNames, N as SUPPORTED_SHELLS, O as aliasActivationHint, P as defaultInstallEnv, R as getSessionIdentity, V as setActiveWorkspace, c as getAutomaticLogsDir, j as slugifyAliasName, k as dedupeAliasName, l as maybeStartProfiling, o as brandHelpArt, t as installCrashHandler, u as profileDir, w as themes, z as listWorkspaces } from "./install-
|
|
2
|
+
import { o as name, s as version, t as createRestClient } from "./rest-B-dizrBw.mjs";
|
|
3
|
+
import { B as resolveSession$1, C as deleteConfig, D as getPreference, E as getLastSeenVersion, G as setLastSeenVersion, H as saveConfig, I as resolveAppUrl, L as resolveChatAuth, M as getStoredApiKeyId, N as getStoredApiKeyWorkspaceName, O as getPromptHistoryPath, P as getUpdateCheckDisabled, R as resolveConfig, S as PREFERENCES, T as getDefaultAgent, U as saveSession, _ as API_KEYS_URL, b as DEFAULT_API_URL, i as resolveAgent$1, j as getShareMachineDefault, v as API_KEY_FAMILY_PREFIX, w as getConfigPath, y as API_KEY_PREFIX, z as resolveManagementAuth } from "./print-SD_y9CTF.mjs";
|
|
4
|
+
import { A as installAgentAlias, B as resolveWorkspaceId, D as machineOsFromPlatform, E as buildImportSeedPrompt, F as detectShell, I as ensureActiveOrganization, L as getActiveWorkspaceId, M as takenAliasNames, N as SUPPORTED_SHELLS, O as aliasActivationHint, P as defaultInstallEnv, R as getSessionIdentity, V as setActiveWorkspace, c as getAutomaticLogsDir, j as slugifyAliasName, k as dedupeAliasName, l as maybeStartProfiling, o as brandHelpArt, t as installCrashHandler, u as profileDir, w as themes, z as listWorkspaces } from "./install-BOtarTEl.mjs";
|
|
5
5
|
import { n as printError, r as printTable, t as output } from "./output-C9mb3sUB.mjs";
|
|
6
6
|
import { t as HttpError } from "./http-error-BF2NZZE3.mjs";
|
|
7
7
|
import "./billing-blocked-D3l5kJlX.mjs";
|
|
8
|
-
import { r as resolveMachineIdentity } from "./client-
|
|
9
|
-
import {
|
|
8
|
+
import { r as resolveMachineIdentity } from "./client-C2lPer4b.mjs";
|
|
9
|
+
import { d as daemonPaths, i as queryDaemonStatus, n as ensureDaemonRunning, s as stopDaemon, u as PORTAL_DAEMON_FLAG } from "./daemon-B7Wv4P6W.mjs";
|
|
10
10
|
import "./tls-cert-CV-pwxVN.mjs";
|
|
11
11
|
import { i as grantPortalAccess, n as fetchPortalDevices, o as registerPortalDevice, r as findThisDevice, s as revokePortalAccess } from "./api-TwLD7ibI.mjs";
|
|
12
12
|
import { a as encodeFsRequest, i as decodeFsResponse, n as SANDBOX_STREAM_PATH, r as streamSpecToQuery, t as SandboxStream } from "./client-Btq6bMzX.mjs";
|
|
@@ -1588,7 +1588,7 @@ const importCommand = {
|
|
|
1588
1588
|
process.exit(1);
|
|
1589
1589
|
}
|
|
1590
1590
|
}
|
|
1591
|
-
const { runChat } = await import("./boot-
|
|
1591
|
+
const { runChat } = await import("./boot-BoqVFa4M.mjs");
|
|
1592
1592
|
await runChat({
|
|
1593
1593
|
appUrl,
|
|
1594
1594
|
sessionToken: session.value.sessionToken,
|
|
@@ -1618,7 +1618,7 @@ async function runImportPrintMode({ argv, appUrl }) {
|
|
|
1618
1618
|
printError(`${session.error.message} For non-interactive use, run \`skydive auth login --web\` first, or set SKYDIVE_SESSION_TOKEN.`);
|
|
1619
1619
|
process.exit(1);
|
|
1620
1620
|
}
|
|
1621
|
-
const { connectMachineShare } = await import("./print-share-
|
|
1621
|
+
const { connectMachineShare } = await import("./print-share-D20YvVbb.mjs");
|
|
1622
1622
|
const machineShare = await connectMachineShare({
|
|
1623
1623
|
appUrl,
|
|
1624
1624
|
sessionToken: session.value.sessionToken,
|
|
@@ -1626,7 +1626,7 @@ async function runImportPrintMode({ argv, appUrl }) {
|
|
|
1626
1626
|
});
|
|
1627
1627
|
const extra = (argv.print ?? "").trim();
|
|
1628
1628
|
const prompt = buildImportSeedPrompt(process.cwd(), machineOsFromPlatform(process.platform)) + (extra ? `\n\nAdditional instructions: ${extra}` : "");
|
|
1629
|
-
const { runPrint } = await import("./print-
|
|
1629
|
+
const { runPrint } = await import("./print-ejS3eAZz.mjs");
|
|
1630
1630
|
try {
|
|
1631
1631
|
const result = await runPrint({
|
|
1632
1632
|
appUrl,
|
|
@@ -2033,7 +2033,7 @@ const chatCommand = {
|
|
|
2033
2033
|
sessionToken: auth.value.token,
|
|
2034
2034
|
agentSelector: argv.agent ?? null
|
|
2035
2035
|
});
|
|
2036
|
-
const { runChat } = await import("./boot-
|
|
2036
|
+
const { runChat } = await import("./boot-BoqVFa4M.mjs");
|
|
2037
2037
|
await runChat({
|
|
2038
2038
|
appUrl: auth.value.appUrl,
|
|
2039
2039
|
sessionToken: auth.value.token,
|
|
@@ -2117,7 +2117,7 @@ async function runPrintMode({ argv, appUrl }) {
|
|
|
2117
2117
|
printError(`${auth.error.message} For non-interactive use, run \`skydive auth login\` first, or set SKYDIVE_API_KEY / SKYDIVE_SESSION_TOKEN.`);
|
|
2118
2118
|
process.exit(1);
|
|
2119
2119
|
}
|
|
2120
|
-
const { runPrint, readStdin } = await import("./print-
|
|
2120
|
+
const { runPrint, readStdin } = await import("./print-ejS3eAZz.mjs");
|
|
2121
2121
|
const workspaceId = await resolveWorkspaceFlag({
|
|
2122
2122
|
appUrl,
|
|
2123
2123
|
sessionToken: auth.value.token,
|
|
@@ -2147,7 +2147,7 @@ async function runPrintMode({ argv, appUrl }) {
|
|
|
2147
2147
|
process.exit(1);
|
|
2148
2148
|
}
|
|
2149
2149
|
} else {
|
|
2150
|
-
const { connectMachineShare } = await import("./print-share-
|
|
2150
|
+
const { connectMachineShare } = await import("./print-share-D20YvVbb.mjs");
|
|
2151
2151
|
machineShare = await connectMachineShare({
|
|
2152
2152
|
appUrl: auth.value.appUrl,
|
|
2153
2153
|
sessionToken: auth.value.token,
|
|
@@ -2212,7 +2212,7 @@ const getCommand$1 = {
|
|
|
2212
2212
|
printError(`${auth.error.message} Run \`skydive auth login\` first, or set SKYDIVE_API_KEY / SKYDIVE_SESSION_TOKEN.`);
|
|
2213
2213
|
process.exit(1);
|
|
2214
2214
|
}
|
|
2215
|
-
const { messageGet } = await import("./print-
|
|
2215
|
+
const { messageGet } = await import("./print-ejS3eAZz.mjs");
|
|
2216
2216
|
try {
|
|
2217
2217
|
const result = await messageGet({
|
|
2218
2218
|
appUrl: auth.value.appUrl,
|
|
@@ -2415,7 +2415,7 @@ const switchCommand = {
|
|
|
2415
2415
|
printError("The workspace picker needs the Bun runtime and it could not be set up automatically. Pass a workspace slug instead, or install Bun and retry.");
|
|
2416
2416
|
process.exit(1);
|
|
2417
2417
|
}
|
|
2418
|
-
const { runWorkspacePicker } = await import("./boot-
|
|
2418
|
+
const { runWorkspacePicker } = await import("./boot-BoqVFa4M.mjs");
|
|
2419
2419
|
await runWorkspacePicker(session);
|
|
2420
2420
|
return;
|
|
2421
2421
|
}
|
|
@@ -2494,7 +2494,7 @@ const openCommand = {
|
|
|
2494
2494
|
const agent = argv.agent ? resolveAgent$1((await fetchPortalDevices(session)).agents, argv.agent) : null;
|
|
2495
2495
|
const cwd = argv.cwd ? path.resolve(argv.cwd) : process.cwd();
|
|
2496
2496
|
const { machineName } = await resolveMachineIdentity(null);
|
|
2497
|
-
const { PortalDaemonClient } = await import("./daemon-client-
|
|
2497
|
+
const { PortalDaemonClient } = await import("./daemon-client-DL3eNr6N.mjs");
|
|
2498
2498
|
let lastLine = "";
|
|
2499
2499
|
let signalConnected;
|
|
2500
2500
|
const connected = new Promise((resolve) => {
|
|
@@ -2808,8 +2808,8 @@ const sandboxCommand = {
|
|
|
2808
2808
|
}).example("skydive sandbox --agent grace", "Live terminal (Ctrl-] detaches)").example("skydive sandbox --agent grace -- tail -n 50 /tmp/harness.log", "One-shot command (use `--` so its flags reach the sandbox)").example("skydive sandbox --agent grace -- sh -c 'ls /tmp | wc -l'", "Shell features go through an explicit `sh -c`"),
|
|
2809
2809
|
handler: async (argv) => {
|
|
2810
2810
|
const session = requireSession(argv);
|
|
2811
|
-
const { createRestClient } = await import("./rest-
|
|
2812
|
-
const { resolveAgent } = await import("./print-
|
|
2811
|
+
const { createRestClient } = await import("./rest-Cz-aN-ME.mjs");
|
|
2812
|
+
const { resolveAgent } = await import("./print-ejS3eAZz.mjs");
|
|
2813
2813
|
const client = createRestClient({
|
|
2814
2814
|
appUrl: session.appUrl,
|
|
2815
2815
|
sessionToken: session.sessionToken
|
|
@@ -3077,8 +3077,8 @@ const fsEditCommand = {
|
|
|
3077
3077
|
handler: async (argv) => {
|
|
3078
3078
|
const session = requireSession(argv);
|
|
3079
3079
|
const remotePath = argv.path;
|
|
3080
|
-
const { createRestClient } = await import("./rest-
|
|
3081
|
-
const { resolveAgent } = await import("./print-
|
|
3080
|
+
const { createRestClient } = await import("./rest-Cz-aN-ME.mjs");
|
|
3081
|
+
const { resolveAgent } = await import("./print-ejS3eAZz.mjs");
|
|
3082
3082
|
const client = createRestClient({
|
|
3083
3083
|
appUrl: session.appUrl,
|
|
3084
3084
|
sessionToken: session.sessionToken
|
|
@@ -4548,7 +4548,7 @@ if (process.argv.includes(UPDATE_WORKER_FLAG)) {
|
|
|
4548
4548
|
process.exit(0);
|
|
4549
4549
|
}
|
|
4550
4550
|
if (process.argv.includes(PORTAL_DAEMON_FLAG)) {
|
|
4551
|
-
const { runPortalDaemon } = await import("./daemon-
|
|
4551
|
+
const { runPortalDaemon } = await import("./daemon-B57zYVAN.mjs");
|
|
4552
4552
|
runPortalDaemon(process.argv);
|
|
4553
4553
|
} else runCli();
|
|
4554
4554
|
function runCli() {
|
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { a as isRecord, i as errorMessage, n as errorDetail, r as sendErrorMessage, t as createRestClient } from "./rest-
|
|
3
|
-
import { A as getSavedTheme, F as recordDefaultAgent, V as resolveWebUrl, W as saveTheme, b as DEFAULT_API_URL, c as cardActionErrorMessage, d as reconcileMaskedInput, f as resolveConnectUrl, g as specKeyFor, h as parseConnectCard, i as resolveAgent, k as getReviewStateDir, l as parseExternalOauthConnectParams, m as computeSettledLabel, p as composeQuestionAnswer, s as MASK_CHAR, u as parseOauthConnectParams, w as getConfigPath, x as DEFAULT_APP_URL } from "./print-
|
|
4
|
-
import { A as installAgentAlias, C as themeVersion, D as machineOsFromPlatform, F as detectShell, L as getActiveWorkspaceId, M as takenAliasNames, O as aliasActivationHint, S as themeModeFromColorFgBg, T as themesForMode, V as setActiveWorkspace, _ as monoTheme, a as WORDMARK, b as themeForMode, d as profilingEnabled, f as record, g as findTheme, h as applyTheme, i as MARK_CELLS, j as slugifyAliasName, k as dedupeAliasName, m as DEFAULT_THEME_ID, n as buildCrashReport, p as writeArtifact, r as writeCrashReport, s as splashFitsWidth, t as installCrashHandler, v as noColorRequested, x as themeMode, y as theme, z as listWorkspaces } from "./install-
|
|
2
|
+
import { a as isRecord, i as errorMessage, n as errorDetail, r as sendErrorMessage, t as createRestClient } from "./rest-B-dizrBw.mjs";
|
|
3
|
+
import { A as getSavedTheme, F as recordDefaultAgent, V as resolveWebUrl, W as saveTheme, b as DEFAULT_API_URL, c as cardActionErrorMessage, d as reconcileMaskedInput, f as resolveConnectUrl, g as specKeyFor, h as parseConnectCard, i as resolveAgent, k as getReviewStateDir, l as parseExternalOauthConnectParams, m as computeSettledLabel, p as composeQuestionAnswer, s as MASK_CHAR, u as parseOauthConnectParams, w as getConfigPath, x as DEFAULT_APP_URL } from "./print-SD_y9CTF.mjs";
|
|
4
|
+
import { A as installAgentAlias, C as themeVersion, D as machineOsFromPlatform, F as detectShell, L as getActiveWorkspaceId, M as takenAliasNames, O as aliasActivationHint, S as themeModeFromColorFgBg, T as themesForMode, V as setActiveWorkspace, _ as monoTheme, a as WORDMARK, b as themeForMode, d as profilingEnabled, f as record, g as findTheme, h as applyTheme, i as MARK_CELLS, j as slugifyAliasName, k as dedupeAliasName, m as DEFAULT_THEME_ID, n as buildCrashReport, p as writeArtifact, r as writeCrashReport, s as splashFitsWidth, t as installCrashHandler, v as noColorRequested, x as themeMode, y as theme, z as listWorkspaces } from "./install-BOtarTEl.mjs";
|
|
5
5
|
import { t as HttpError } from "./http-error-BF2NZZE3.mjs";
|
|
6
6
|
import { i as billingBlockedOutcomeFromSendResponse } from "./billing-blocked-D3l5kJlX.mjs";
|
|
7
|
-
import { t as PortalClient } from "./client-
|
|
8
|
-
import "./daemon-
|
|
7
|
+
import { t as PortalClient } from "./client-C2lPer4b.mjs";
|
|
8
|
+
import "./daemon-B7Wv4P6W.mjs";
|
|
9
9
|
import { t as defaultTlsCertSource } from "./tls-cert-CV-pwxVN.mjs";
|
|
10
10
|
import "./api-TwLD7ibI.mjs";
|
|
11
11
|
import { t as SandboxStream } from "./client-Btq6bMzX.mjs";
|
|
12
|
-
import { t as PortalDaemonClient } from "./daemon-client-
|
|
12
|
+
import { t as PortalDaemonClient } from "./daemon-client-Btr9rmKl.mjs";
|
|
13
13
|
import { t as runRawPtyPassthrough } from "./raw-pty-D5PhKZSl.mjs";
|
|
14
14
|
import * as os$1 from "node:os";
|
|
15
15
|
import { homedir, platform, release, tmpdir } from "node:os";
|
|
@@ -4702,7 +4702,11 @@ const maxLines = 6;
|
|
|
4702
4702
|
function ToolRead({ item }) {
|
|
4703
4703
|
const glyph = toolGlyph(item.state);
|
|
4704
4704
|
const input = isRecord(item.input) ? item.input : {};
|
|
4705
|
-
const filePath =
|
|
4705
|
+
const filePath = pickString(input, [
|
|
4706
|
+
"path",
|
|
4707
|
+
"filePath",
|
|
4708
|
+
"file"
|
|
4709
|
+
]);
|
|
4706
4710
|
const offset = typeof input.offset === "number" ? input.offset : null;
|
|
4707
4711
|
const limit = typeof input.limit === "number" ? input.limit : null;
|
|
4708
4712
|
const header = filePath ? offset !== null ? `${filePath}:L${offset}${limit ? `-L${offset + limit - 1}` : ""}` : filePath : "(unknown path)";
|
|
@@ -7593,6 +7597,7 @@ function usePromptHistory({ filePath }) {
|
|
|
7593
7597
|
const entriesRef = useRef([]);
|
|
7594
7598
|
const indexRef = useRef(0);
|
|
7595
7599
|
const shownRef = useRef(null);
|
|
7600
|
+
const draftRef = useRef("");
|
|
7596
7601
|
useEffect(() => {
|
|
7597
7602
|
if (!filePath) return;
|
|
7598
7603
|
let cancelled = false;
|
|
@@ -7613,6 +7618,7 @@ function usePromptHistory({ filePath }) {
|
|
|
7613
7618
|
const after = withPromptEntry(before, raw);
|
|
7614
7619
|
indexRef.current = 0;
|
|
7615
7620
|
shownRef.current = null;
|
|
7621
|
+
draftRef.current = "";
|
|
7616
7622
|
if (after === before) return;
|
|
7617
7623
|
const trimmed = after.length < before.length + 1;
|
|
7618
7624
|
entriesRef.current = after;
|
|
@@ -7628,6 +7634,7 @@ function usePromptHistory({ filePath }) {
|
|
|
7628
7634
|
if (isEdited(indexRef.current, shownRef.current, currentText)) return null;
|
|
7629
7635
|
const result = recallPrevious(entriesRef.current, indexRef.current);
|
|
7630
7636
|
if (!result) return null;
|
|
7637
|
+
if (indexRef.current === 0) draftRef.current = currentText;
|
|
7631
7638
|
indexRef.current = result.index;
|
|
7632
7639
|
shownRef.current = result.text;
|
|
7633
7640
|
return result.text;
|
|
@@ -7637,7 +7644,11 @@ function usePromptHistory({ filePath }) {
|
|
|
7637
7644
|
const result = recallNext(entriesRef.current, indexRef.current);
|
|
7638
7645
|
if (!result) return null;
|
|
7639
7646
|
indexRef.current = result.index;
|
|
7640
|
-
|
|
7647
|
+
if (result.index === 0) {
|
|
7648
|
+
shownRef.current = null;
|
|
7649
|
+
return draftRef.current;
|
|
7650
|
+
}
|
|
7651
|
+
shownRef.current = result.text;
|
|
7641
7652
|
return result.text;
|
|
7642
7653
|
}, [])
|
|
7643
7654
|
};
|
|
@@ -568,6 +568,8 @@ var PortalClient = class {
|
|
|
568
568
|
legacyGrantsChecked = false;
|
|
569
569
|
wakeWatch = null;
|
|
570
570
|
lastTick = Date.now();
|
|
571
|
+
handingOff = false;
|
|
572
|
+
handoffResolve = null;
|
|
571
573
|
heartbeatConfig;
|
|
572
574
|
constructor(opts) {
|
|
573
575
|
this.opts = opts;
|
|
@@ -641,6 +643,48 @@ var PortalClient = class {
|
|
|
641
643
|
this.jobs = null;
|
|
642
644
|
this.ws?.close();
|
|
643
645
|
this.ws = null;
|
|
646
|
+
this.finishHandoff();
|
|
647
|
+
}
|
|
648
|
+
/**
|
|
649
|
+
* Begin a make-before-break handoff to a successor daemon. Keep the current
|
|
650
|
+
* portal socket (and thus this machine's presence claim) alive so there is no
|
|
651
|
+
* `registered but not connected` gap, but stop redialing once it closes — the
|
|
652
|
+
* successor's `claim()` supersedes us and the server closes our socket, which
|
|
653
|
+
* is our cue to step down. Resolves when the socket has closed (clean
|
|
654
|
+
* handoff) OR after `timeoutMs` (the successor never came up, so we release
|
|
655
|
+
* the slot rather than hold it forever). Idempotent.
|
|
656
|
+
*/
|
|
657
|
+
beginHandoff(timeoutMs) {
|
|
658
|
+
if (this.disposed) return Promise.resolve();
|
|
659
|
+
if (this.handingOff) return new Promise((resolve) => {
|
|
660
|
+
const prev = this.handoffResolve;
|
|
661
|
+
this.handoffResolve = () => {
|
|
662
|
+
prev?.();
|
|
663
|
+
resolve();
|
|
664
|
+
};
|
|
665
|
+
});
|
|
666
|
+
this.handingOff = true;
|
|
667
|
+
if (!this.ws) return Promise.resolve();
|
|
668
|
+
return new Promise((resolve) => {
|
|
669
|
+
let done = false;
|
|
670
|
+
const finish = () => {
|
|
671
|
+
if (done) return;
|
|
672
|
+
done = true;
|
|
673
|
+
clearTimeout(timer);
|
|
674
|
+
resolve();
|
|
675
|
+
};
|
|
676
|
+
this.handoffResolve = finish;
|
|
677
|
+
const timer = setTimeout(() => {
|
|
678
|
+
this.disable();
|
|
679
|
+
this.finishHandoff();
|
|
680
|
+
}, timeoutMs);
|
|
681
|
+
if (typeof timer.unref === "function") timer.unref();
|
|
682
|
+
});
|
|
683
|
+
}
|
|
684
|
+
finishHandoff() {
|
|
685
|
+
const resolve = this.handoffResolve;
|
|
686
|
+
this.handoffResolve = null;
|
|
687
|
+
resolve?.();
|
|
644
688
|
}
|
|
645
689
|
/**
|
|
646
690
|
* Grant one agent access to this machine (default-deny; user-initiated).
|
|
@@ -702,18 +746,18 @@ var PortalClient = class {
|
|
|
702
746
|
this.opts.onMachineName(identity.machineName, identity.machineNameSource);
|
|
703
747
|
}
|
|
704
748
|
let backoff = this.heartbeatConfig.initialBackoffMs;
|
|
705
|
-
while (this.enabled && !this.disposed) {
|
|
749
|
+
while (this.enabled && !this.disposed && !this.handingOff) {
|
|
706
750
|
this.setStatus("connecting");
|
|
707
751
|
try {
|
|
708
752
|
const token = await this.wsToken();
|
|
709
753
|
if (await this.runConnection(token)) backoff = this.heartbeatConfig.initialBackoffMs;
|
|
710
754
|
else backoff = Math.min(backoff * 2, this.heartbeatConfig.maxBackoffMs);
|
|
711
755
|
} catch (err) {
|
|
712
|
-
if (!this.enabled || this.disposed) break;
|
|
756
|
+
if (!this.enabled || this.disposed || this.handingOff) break;
|
|
713
757
|
this.setStatus("error", errorMessage(err));
|
|
714
758
|
backoff = Math.min(backoff * 2, this.heartbeatConfig.maxBackoffMs);
|
|
715
759
|
}
|
|
716
|
-
if (!this.enabled || this.disposed) break;
|
|
760
|
+
if (!this.enabled || this.disposed || this.handingOff) break;
|
|
717
761
|
await sleep(Math.random() * backoff);
|
|
718
762
|
}
|
|
719
763
|
}
|
|
@@ -772,6 +816,7 @@ var PortalClient = class {
|
|
|
772
816
|
jobs.killAll();
|
|
773
817
|
if (this.jobs === jobs) this.jobs = null;
|
|
774
818
|
if (this.ws === ws) this.ws = null;
|
|
819
|
+
if (this.handingOff) this.finishHandoff();
|
|
775
820
|
settle();
|
|
776
821
|
});
|
|
777
822
|
});
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import "./client-C2lPer4b.mjs";
|
|
3
|
+
import { a as runPortalDaemon, c as stopDaemonForHandoff, i as queryDaemonStatus, n as ensureDaemonRunning, o as startPortalDaemon, r as isDaemonListening, s as stopDaemon, t as PortalDaemon } from "./daemon-B7Wv4P6W.mjs";
|
|
4
|
+
import "./tls-cert-CV-pwxVN.mjs";
|
|
5
|
+
import "./api-TwLD7ibI.mjs";
|
|
6
|
+
|
|
7
|
+
export { runPortalDaemon };
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { n as isRecord, t as PortalClient } from "./client-
|
|
2
|
+
import { n as isRecord, t as PortalClient } from "./client-C2lPer4b.mjs";
|
|
3
3
|
import { t as defaultTlsCertSource } from "./tls-cert-CV-pwxVN.mjs";
|
|
4
4
|
import os from "node:os";
|
|
5
5
|
import path from "node:path";
|
|
@@ -21,7 +21,7 @@ import { access, appendFile, mkdir, readFile, unlink, writeFile } from "node:fs/
|
|
|
21
21
|
* the commit time of the built tree is one monotonic clock they share.
|
|
22
22
|
*/
|
|
23
23
|
function portalDaemonBuild() {
|
|
24
|
-
return "
|
|
24
|
+
return "1787070117";
|
|
25
25
|
}
|
|
26
26
|
/**
|
|
27
27
|
* Whether a client carrying `mine` should replace a running daemon carrying
|
|
@@ -220,6 +220,20 @@ const clientStatusSchema = z.object({ t: z.literal("status") });
|
|
|
220
220
|
* exits — cutting live exec/tunnel connections, not just refusing new ones.
|
|
221
221
|
*/
|
|
222
222
|
const clientShutdownSchema = z.object({ t: z.literal("shutdown") });
|
|
223
|
+
/**
|
|
224
|
+
* `shutdown_handoff` — a newer daemon taking over (self-update / newest-build-
|
|
225
|
+
* wins) asks the incumbent to step down WITHOUT dropping the portal connection
|
|
226
|
+
* first. The incumbent frees the control socket and disconnects attached CLIs
|
|
227
|
+
* (they reconnect to the new daemon in ~500ms, unchanged), but KEEPS its
|
|
228
|
+
* outbound portal WebSocket — and thus its presence claim — alive until the
|
|
229
|
+
* new daemon has dialled out and claimed the machine's presence slot, at which
|
|
230
|
+
* point the server supersedes the incumbent's socket and it exits. This is a
|
|
231
|
+
* make-before-break handoff: the presence key is owned by one daemon or the
|
|
232
|
+
* other for the entire takeover, so `exec`/`file write` never sees a
|
|
233
|
+
* `registered but not connected` gap mid-upgrade. A bounded fallback timeout
|
|
234
|
+
* ensures the incumbent still exits if the successor never comes up.
|
|
235
|
+
*/
|
|
236
|
+
const clientShutdownHandoffSchema = z.object({ t: z.literal("shutdown_handoff") });
|
|
223
237
|
const clientMessageSchema = z.discriminatedUnion("t", [
|
|
224
238
|
clientHelloSchema,
|
|
225
239
|
clientBindSchema,
|
|
@@ -230,7 +244,8 @@ const clientMessageSchema = z.discriminatedUnion("t", [
|
|
|
230
244
|
clientDeclineSchema,
|
|
231
245
|
clientByeSchema,
|
|
232
246
|
clientStatusSchema,
|
|
233
|
-
clientShutdownSchema
|
|
247
|
+
clientShutdownSchema,
|
|
248
|
+
clientShutdownHandoffSchema
|
|
234
249
|
]);
|
|
235
250
|
/**
|
|
236
251
|
* `state` — the shared portal status, pushed to every attached client so each
|
|
@@ -357,6 +372,14 @@ function parseDaemonMessage(line) {
|
|
|
357
372
|
*/
|
|
358
373
|
/** Grace period after the last client detaches before the daemon exits. */
|
|
359
374
|
const IDLE_SHUTDOWN_MS = 3e4;
|
|
375
|
+
/**
|
|
376
|
+
* Upper bound an incumbent daemon waits, during a graceful handoff, for the
|
|
377
|
+
* successor to dial out and claim presence before giving up and releasing the
|
|
378
|
+
* slot itself. Comfortably above a normal successor boot + WS dial (a few
|
|
379
|
+
* hundred ms to a couple of seconds), and short enough that a failed successor
|
|
380
|
+
* doesn't strand the machine as reachable-but-dead for long.
|
|
381
|
+
*/
|
|
382
|
+
const HANDOFF_MAX_WAIT_MS = 1e4;
|
|
360
383
|
var PortalDaemon = class {
|
|
361
384
|
appUrl;
|
|
362
385
|
paths;
|
|
@@ -369,6 +392,7 @@ var PortalDaemon = class {
|
|
|
369
392
|
persistedMachineName = null;
|
|
370
393
|
fallbackCwd;
|
|
371
394
|
idleTimer = null;
|
|
395
|
+
handingOff = false;
|
|
372
396
|
sessionToken = null;
|
|
373
397
|
deviceToken = null;
|
|
374
398
|
constructor(appUrl) {
|
|
@@ -478,6 +502,9 @@ var PortalDaemon = class {
|
|
|
478
502
|
case "shutdown":
|
|
479
503
|
this.forceShutdown();
|
|
480
504
|
return;
|
|
505
|
+
case "shutdown_handoff":
|
|
506
|
+
this.gracefulHandoffShutdown();
|
|
507
|
+
return;
|
|
481
508
|
default: return msg;
|
|
482
509
|
}
|
|
483
510
|
}
|
|
@@ -615,6 +642,37 @@ var PortalDaemon = class {
|
|
|
615
642
|
this.server?.close();
|
|
616
643
|
process.exit(0);
|
|
617
644
|
}
|
|
645
|
+
/**
|
|
646
|
+
* Graceful takeover step-down (a newer daemon is replacing us). Unlike
|
|
647
|
+
* `forceShutdown`, this does NOT drop the portal connection up front: doing so
|
|
648
|
+
* would let this machine's presence key expire (10s TTL) before the successor
|
|
649
|
+
* dials out and re-claims it, which is the `registered but not connected` gap
|
|
650
|
+
* that made `exec`/`file write` fail mid-self-update.
|
|
651
|
+
*
|
|
652
|
+
* Order matters. We free the control socket and disconnect attached CLIs
|
|
653
|
+
* first, because the successor cannot bind the singleton socket (and thus
|
|
654
|
+
* cannot start its own portal client) until we release it. But we KEEP our
|
|
655
|
+
* outbound portal WebSocket — and its presence claim — alive across that
|
|
656
|
+
* window. When the successor connects and `claim()`s the slot, the server
|
|
657
|
+
* supersedes us and closes our socket; `beginHandoff` resolves on that close
|
|
658
|
+
* and we exit. A bounded fallback inside `beginHandoff` still exits us if the
|
|
659
|
+
* successor never comes up, so we never hold a dead slot forever.
|
|
660
|
+
*/
|
|
661
|
+
async gracefulHandoffShutdown() {
|
|
662
|
+
if (this.handingOff) return;
|
|
663
|
+
this.handingOff = true;
|
|
664
|
+
for (const conn of this.conns) try {
|
|
665
|
+
conn.socket.destroy();
|
|
666
|
+
} catch (_error) {}
|
|
667
|
+
this.conns.clear();
|
|
668
|
+
this.server?.close();
|
|
669
|
+
this.server = null;
|
|
670
|
+
if (this.client) {
|
|
671
|
+
await this.client.beginHandoff(HANDOFF_MAX_WAIT_MS);
|
|
672
|
+
this.client.dispose();
|
|
673
|
+
}
|
|
674
|
+
process.exit(0);
|
|
675
|
+
}
|
|
618
676
|
async loadState() {
|
|
619
677
|
try {
|
|
620
678
|
const raw = await readFile(this.paths.statePath, "utf8");
|
|
@@ -663,7 +721,7 @@ async function ensureDaemonRunning(appUrl) {
|
|
|
663
721
|
if (await isDaemonListening(socketPath)) {
|
|
664
722
|
const status = await queryDaemonStatus(appUrl);
|
|
665
723
|
if (!status || !isNewerBuild(portalDaemonBuild(), status.build)) return;
|
|
666
|
-
if (await
|
|
724
|
+
if (await stopDaemonForHandoff(appUrl) === "failed") return;
|
|
667
725
|
}
|
|
668
726
|
const entry = process.argv[1];
|
|
669
727
|
const args = entry ? [
|
|
@@ -758,6 +816,37 @@ async function queryDaemonStatus(appUrl) {
|
|
|
758
816
|
});
|
|
759
817
|
}
|
|
760
818
|
/**
|
|
819
|
+
* Ask a running incumbent daemon to step down for a make-before-break handoff
|
|
820
|
+
* (see the daemon's `gracefulHandoffShutdown`). Sends `shutdown_handoff` and
|
|
821
|
+
* waits for the incumbent to release the CONTROL SOCKET — at which point the
|
|
822
|
+
* successor can bind it and start its own portal client. Crucially, the
|
|
823
|
+
* incumbent keeps its portal connection (and presence) alive past this point,
|
|
824
|
+
* so the successor's subsequent `claim()` supersedes it with no presence gap.
|
|
825
|
+
*
|
|
826
|
+
* Returns `stopped` once the socket is free, `failed` if it never freed (the
|
|
827
|
+
* caller should then leave the incumbent alone rather than fight it — same
|
|
828
|
+
* fallback semantics as `stopDaemon`), or `not-running` if nothing was there.
|
|
829
|
+
* Unlike `stopDaemon` this never SIGKILLs: the incumbent is intentionally still
|
|
830
|
+
* alive (holding presence) after it frees the socket, so killing it by pid is
|
|
831
|
+
* exactly the gap this path exists to avoid.
|
|
832
|
+
*/
|
|
833
|
+
async function stopDaemonForHandoff(appUrl) {
|
|
834
|
+
const { socketPath } = daemonPaths(appUrl);
|
|
835
|
+
if (!await isDaemonListening(socketPath)) return "not-running";
|
|
836
|
+
const sock = await connectControl(socketPath);
|
|
837
|
+
if (!sock) return "failed";
|
|
838
|
+
sock.write(encodeLine({ t: "shutdown_handoff" }));
|
|
839
|
+
for (let i = 0; i < 50; i += 1) {
|
|
840
|
+
await sleep(100);
|
|
841
|
+
if (!await isDaemonListening(socketPath)) {
|
|
842
|
+
sock.destroy();
|
|
843
|
+
return "stopped";
|
|
844
|
+
}
|
|
845
|
+
}
|
|
846
|
+
sock.destroy();
|
|
847
|
+
return "failed";
|
|
848
|
+
}
|
|
849
|
+
/**
|
|
761
850
|
* Hard-stop a running daemon. Preferred path: send `shutdown` over the control
|
|
762
851
|
* socket so it drains clients and exits cleanly. If the socket is unresponsive
|
|
763
852
|
* (a wedged daemon), fall back to SIGTERM then SIGKILL by the pid the status
|
|
@@ -809,4 +898,4 @@ function sleep(ms) {
|
|
|
809
898
|
}
|
|
810
899
|
|
|
811
900
|
//#endregion
|
|
812
|
-
export { runPortalDaemon as a,
|
|
901
|
+
export { runPortalDaemon as a, stopDaemonForHandoff as c, daemonPaths as d, encodeLine as f, queryDaemonStatus as i, LOCAL_PROTOCOL_VERSION as l, parseDaemonMessage as m, ensureDaemonRunning as n, startPortalDaemon as o, makeLineParser as p, isDaemonListening as r, stopDaemon as s, PortalDaemon as t, PORTAL_DAEMON_FLAG as u };
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import {
|
|
2
|
+
import { d as daemonPaths, f as encodeLine, l as LOCAL_PROTOCOL_VERSION, m as parseDaemonMessage, n as ensureDaemonRunning, p as makeLineParser } from "./daemon-B7Wv4P6W.mjs";
|
|
3
3
|
import { randomUUID } from "node:crypto";
|
|
4
4
|
import { connect } from "node:net";
|
|
5
5
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { s as version$1 } from "./rest-
|
|
3
|
-
import { w as getConfigPath } from "./print-
|
|
2
|
+
import { s as version$1 } from "./rest-B-dizrBw.mjs";
|
|
3
|
+
import { w as getConfigPath } from "./print-SD_y9CTF.mjs";
|
|
4
4
|
import os from "node:os";
|
|
5
5
|
import path from "node:path";
|
|
6
6
|
import { err, ok } from "neverthrow";
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { a as isRecord, i as errorMessage, r as sendErrorMessage, t as createRestClient } from "./rest-
|
|
2
|
+
import { a as isRecord, i as errorMessage, r as sendErrorMessage, t as createRestClient } from "./rest-B-dizrBw.mjs";
|
|
3
3
|
import { t as HttpError } from "./http-error-BF2NZZE3.mjs";
|
|
4
4
|
import { i as billingBlockedOutcomeFromSendResponse, n as BillingBlockedError } from "./billing-blocked-D3l5kJlX.mjs";
|
|
5
5
|
import path from "node:path";
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import "./rest-
|
|
3
|
-
import { a as runPrint, i as resolveAgent, n as messageGet, o as toPrintError, r as readStdin, t as collectRunText } from "./print-
|
|
2
|
+
import "./rest-B-dizrBw.mjs";
|
|
3
|
+
import { a as runPrint, i as resolveAgent, n as messageGet, o as toPrintError, r as readStdin, t as collectRunText } from "./print-SD_y9CTF.mjs";
|
|
4
4
|
import "./billing-blocked-D3l5kJlX.mjs";
|
|
5
5
|
|
|
6
6
|
export { messageGet, readStdin, resolveAgent, runPrint };
|
|
@@ -14,7 +14,7 @@ import { n as printError } from "./output-C9mb3sUB.mjs";
|
|
|
14
14
|
* the reply (and to --json).
|
|
15
15
|
*/
|
|
16
16
|
async function connectMachineShare({ appUrl, sessionToken, timeoutHint }) {
|
|
17
|
-
const { PortalClient } = await import("./client-
|
|
17
|
+
const { PortalClient } = await import("./client-CSKr4Swa.mjs");
|
|
18
18
|
const { defaultTlsCertSource } = await import("./tls-cert-CLgSQALB.mjs");
|
|
19
19
|
let signalConnected;
|
|
20
20
|
const connected = new Promise((resolve) => {
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { n as errorDetail, r as sendErrorMessage, t as createRestClient } from "./rest-
|
|
2
|
+
import { n as errorDetail, r as sendErrorMessage, t as createRestClient } from "./rest-B-dizrBw.mjs";
|
|
3
3
|
import { t as HttpError } from "./http-error-BF2NZZE3.mjs";
|
|
4
4
|
import "./billing-blocked-D3l5kJlX.mjs";
|
|
5
5
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "skydive-cli",
|
|
3
|
-
"version": "0.5.0-beta.
|
|
3
|
+
"version": "0.5.0-beta.48",
|
|
4
4
|
"description": "Skydive CLI — cloud agents from the command line",
|
|
5
5
|
"homepage": "https://skydive.com",
|
|
6
6
|
"license": "MIT",
|
|
@@ -69,9 +69,9 @@
|
|
|
69
69
|
},
|
|
70
70
|
"//optionalDependencies": "The per-platform binary packages (skydive-cli-<os>-<arch>) are INTENTIONALLY not committed here. They are published by release-skydive-cli-binaries.yml and injected — pinned to the exact version being published — into the published launcher's optionalDependencies at release time (scripts/prepare-platform-packages.mjs --pin-version, run in release-skydive-cli.yml). Committing them would make `yarn install` try to resolve versions that only exist post-publish, breaking local dev and CI. npm installs of a published skydive-cli still get them (os/cpu-gated); the launcher (src/launcher.ts) resolves and execs the matching one. Local dev never needs them: it runs the JS bundle or `build:binary` directly.",
|
|
71
71
|
"optionalDependencies": {
|
|
72
|
-
"skydive-cli-darwin-arm64": "0.5.0-beta.
|
|
73
|
-
"skydive-cli-darwin-x64": "0.5.0-beta.
|
|
74
|
-
"skydive-cli-linux-x64": "0.5.0-beta.
|
|
75
|
-
"skydive-cli-linux-arm64": "0.5.0-beta.
|
|
72
|
+
"skydive-cli-darwin-arm64": "0.5.0-beta.48",
|
|
73
|
+
"skydive-cli-darwin-x64": "0.5.0-beta.48",
|
|
74
|
+
"skydive-cli-linux-x64": "0.5.0-beta.48",
|
|
75
|
+
"skydive-cli-linux-arm64": "0.5.0-beta.48"
|
|
76
76
|
}
|
|
77
77
|
}
|
|
@@ -1,7 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
import "./client-BnpgrtCC.mjs";
|
|
3
|
-
import { a as runPortalDaemon, i as queryDaemonStatus, n as ensureDaemonRunning, o as startPortalDaemon, r as isDaemonListening, s as stopDaemon, t as PortalDaemon } from "./daemon-w8vtZ81t.mjs";
|
|
4
|
-
import "./tls-cert-CV-pwxVN.mjs";
|
|
5
|
-
import "./api-TwLD7ibI.mjs";
|
|
6
|
-
|
|
7
|
-
export { runPortalDaemon };
|