bitfab-cli 0.2.49 → 0.2.51
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/index.js +154 -13
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -7418,6 +7418,7 @@ function buildBitfabRequestHeaders(apiKey, pluginVersion, platform2) {
|
|
|
7418
7418
|
}
|
|
7419
7419
|
|
|
7420
7420
|
// ../bitfab-plugin-lib/dist/activeStudioSession.js
|
|
7421
|
+
import { execFileSync as execFileSync2 } from "child_process";
|
|
7421
7422
|
import crypto3 from "crypto";
|
|
7422
7423
|
import fs6 from "fs";
|
|
7423
7424
|
import os6 from "os";
|
|
@@ -7461,6 +7462,22 @@ function isProcessAlive(pid) {
|
|
|
7461
7462
|
return err.code === "EPERM";
|
|
7462
7463
|
}
|
|
7463
7464
|
}
|
|
7465
|
+
function processStartToken(pid) {
|
|
7466
|
+
try {
|
|
7467
|
+
const out = execFileSync2("ps", ["-o", "lstart=", "-p", String(pid)], {
|
|
7468
|
+
encoding: "utf-8",
|
|
7469
|
+
stdio: ["ignore", "pipe", "ignore"],
|
|
7470
|
+
// Bound it: a local `ps -p` is instant, but with no timeout a wedged `ps`
|
|
7471
|
+
// (severe load, a stuck process table) could block the monitor claim. On
|
|
7472
|
+
// timeout this throws, we return null, and the caller degrades to a plain
|
|
7473
|
+
// liveness check — never a hang.
|
|
7474
|
+
timeout: 2e3
|
|
7475
|
+
}).trim();
|
|
7476
|
+
return out || null;
|
|
7477
|
+
} catch {
|
|
7478
|
+
return null;
|
|
7479
|
+
}
|
|
7480
|
+
}
|
|
7464
7481
|
function clearActiveStudioSession(sessionId, opts = {}) {
|
|
7465
7482
|
try {
|
|
7466
7483
|
const current = readActiveStudioSessionRaw();
|
|
@@ -7500,6 +7517,92 @@ function readActiveStudioSessionRaw() {
|
|
|
7500
7517
|
function readActiveStudioSession() {
|
|
7501
7518
|
return readActiveStudioSessionRaw();
|
|
7502
7519
|
}
|
|
7520
|
+
function monitorLockPath() {
|
|
7521
|
+
return path5.join(os6.homedir(), ".config", "bitfab", `active-studio-session.${sessionScope()}.monitor.lock`);
|
|
7522
|
+
}
|
|
7523
|
+
function tryClaimStudioMonitor() {
|
|
7524
|
+
const lock = monitorLockPath();
|
|
7525
|
+
fs6.mkdirSync(path5.dirname(lock), { recursive: true });
|
|
7526
|
+
for (let attempt = 0; attempt < 10; attempt++) {
|
|
7527
|
+
try {
|
|
7528
|
+
const fd = fs6.openSync(lock, "wx");
|
|
7529
|
+
fs6.writeSync(fd, monitorLockBody(process.pid));
|
|
7530
|
+
fs6.closeSync(fd);
|
|
7531
|
+
stampMonitorPid();
|
|
7532
|
+
return true;
|
|
7533
|
+
} catch (err) {
|
|
7534
|
+
if (err.code !== "EEXIST") {
|
|
7535
|
+
throw err;
|
|
7536
|
+
}
|
|
7537
|
+
const owner = readLockOwner(lock);
|
|
7538
|
+
if (owner?.pid === process.pid) {
|
|
7539
|
+
return true;
|
|
7540
|
+
}
|
|
7541
|
+
if (owner && isMonitorOwnerLive(owner)) {
|
|
7542
|
+
return false;
|
|
7543
|
+
}
|
|
7544
|
+
try {
|
|
7545
|
+
fs6.unlinkSync(lock);
|
|
7546
|
+
} catch {
|
|
7547
|
+
}
|
|
7548
|
+
}
|
|
7549
|
+
}
|
|
7550
|
+
stampMonitorPid();
|
|
7551
|
+
return true;
|
|
7552
|
+
}
|
|
7553
|
+
function claimStudioMonitor() {
|
|
7554
|
+
try {
|
|
7555
|
+
const lock = monitorLockPath();
|
|
7556
|
+
fs6.mkdirSync(path5.dirname(lock), { recursive: true });
|
|
7557
|
+
fs6.writeFileSync(lock, monitorLockBody(process.pid));
|
|
7558
|
+
} catch {
|
|
7559
|
+
}
|
|
7560
|
+
stampMonitorPid();
|
|
7561
|
+
}
|
|
7562
|
+
function releaseStudioMonitor() {
|
|
7563
|
+
try {
|
|
7564
|
+
const lock = monitorLockPath();
|
|
7565
|
+
if (readLockOwner(lock)?.pid === process.pid) {
|
|
7566
|
+
fs6.unlinkSync(lock);
|
|
7567
|
+
}
|
|
7568
|
+
} catch {
|
|
7569
|
+
}
|
|
7570
|
+
}
|
|
7571
|
+
function monitorLockBody(pid) {
|
|
7572
|
+
return `${pid}
|
|
7573
|
+
${processStartToken(pid) ?? ""}`;
|
|
7574
|
+
}
|
|
7575
|
+
function isMonitorOwnerLive(owner) {
|
|
7576
|
+
if (!isProcessAlive(owner.pid)) {
|
|
7577
|
+
return false;
|
|
7578
|
+
}
|
|
7579
|
+
if (!owner.start) {
|
|
7580
|
+
return true;
|
|
7581
|
+
}
|
|
7582
|
+
const current = processStartToken(owner.pid);
|
|
7583
|
+
if (current === null) {
|
|
7584
|
+
return true;
|
|
7585
|
+
}
|
|
7586
|
+
return current === owner.start;
|
|
7587
|
+
}
|
|
7588
|
+
function readLockOwner(lock) {
|
|
7589
|
+
try {
|
|
7590
|
+
const [pidLine = "", ...rest] = fs6.readFileSync(lock, "utf-8").split("\n");
|
|
7591
|
+
const pid = Number.parseInt(pidLine.trim(), 10);
|
|
7592
|
+
if (!Number.isInteger(pid)) {
|
|
7593
|
+
return null;
|
|
7594
|
+
}
|
|
7595
|
+
return { pid, start: rest.join("\n").trim() };
|
|
7596
|
+
} catch {
|
|
7597
|
+
return null;
|
|
7598
|
+
}
|
|
7599
|
+
}
|
|
7600
|
+
function stampMonitorPid() {
|
|
7601
|
+
const current = readActiveStudioSessionRaw();
|
|
7602
|
+
if (current) {
|
|
7603
|
+
writeRecord({ ...current, pid: process.pid });
|
|
7604
|
+
}
|
|
7605
|
+
}
|
|
7503
7606
|
|
|
7504
7607
|
// ../bitfab-plugin-lib/dist/studioTeardown.js
|
|
7505
7608
|
var WINDOW_CLOSE_GRACE_PERIOD_MS = 1e4;
|
|
@@ -7859,13 +7962,13 @@ async function reportHandoff(apiKey, pluginVersion, platform2, body) {
|
|
|
7859
7962
|
import crypto4 from "crypto";
|
|
7860
7963
|
|
|
7861
7964
|
// ../bitfab-plugin-lib/dist/frontmostApp.js
|
|
7862
|
-
import { execFileSync as
|
|
7965
|
+
import { execFileSync as execFileSync3, spawn as spawn2 } from "child_process";
|
|
7863
7966
|
import os8 from "os";
|
|
7864
7967
|
function findAncestorApp() {
|
|
7865
7968
|
try {
|
|
7866
7969
|
let pid = process.ppid;
|
|
7867
7970
|
while (pid > 1) {
|
|
7868
|
-
const output =
|
|
7971
|
+
const output = execFileSync3("ps", ["-o", "ppid=,comm=", "-p", String(pid)], { stdio: "pipe", encoding: "utf-8" }).trim();
|
|
7869
7972
|
const match = output.match(/^\s*(\d+)\s+(.+)$/);
|
|
7870
7973
|
if (!match) {
|
|
7871
7974
|
break;
|
|
@@ -7924,19 +8027,19 @@ function getFrontmostApp() {
|
|
|
7924
8027
|
const platform2 = os8.platform();
|
|
7925
8028
|
try {
|
|
7926
8029
|
if (platform2 === "darwin") {
|
|
7927
|
-
return
|
|
8030
|
+
return execFileSync3("osascript", [
|
|
7928
8031
|
"-e",
|
|
7929
8032
|
'tell application "System Events" to get name of first process whose frontmost is true'
|
|
7930
8033
|
], { stdio: "pipe", encoding: "utf-8" }).trim();
|
|
7931
8034
|
}
|
|
7932
8035
|
if (platform2 === "linux") {
|
|
7933
|
-
return
|
|
8036
|
+
return execFileSync3("xdotool", ["getactivewindow"], {
|
|
7934
8037
|
stdio: "pipe",
|
|
7935
8038
|
encoding: "utf-8"
|
|
7936
8039
|
}).trim();
|
|
7937
8040
|
}
|
|
7938
8041
|
if (platform2 === "win32") {
|
|
7939
|
-
return
|
|
8042
|
+
return execFileSync3("powershell.exe", [
|
|
7940
8043
|
"-NoProfile",
|
|
7941
8044
|
"-Command",
|
|
7942
8045
|
`Add-Type -MemberDefinition '[DllImport("user32.dll")] public static extern IntPtr GetForegroundWindow();' -Name WinFocus -Namespace Bitfab; [Bitfab.WinFocus]::GetForegroundWindow().ToInt64()`
|
|
@@ -8115,7 +8218,7 @@ async function openStudioTo(path15, opts = {}) {
|
|
|
8115
8218
|
serviceUrl,
|
|
8116
8219
|
windowPid: windowPid ?? void 0
|
|
8117
8220
|
});
|
|
8118
|
-
const poller2 = startEventPoller(serviceUrl, loginApiKey, sessionId2, opts.onEvent, restoreFocus);
|
|
8221
|
+
const poller2 = startEventPoller(serviceUrl, loginApiKey, sessionId2, opts.onEvent, restoreFocus, { claim: opts.monitor !== "wait" });
|
|
8119
8222
|
return {
|
|
8120
8223
|
sessionId: sessionId2,
|
|
8121
8224
|
serviceUrl,
|
|
@@ -8145,13 +8248,34 @@ async function openStudioTo(path15, opts = {}) {
|
|
|
8145
8248
|
if (!ack.acked) {
|
|
8146
8249
|
throw new StudioNavigationError(ack.reason ?? "unknown", ack.blockedReason, existing.sessionId);
|
|
8147
8250
|
}
|
|
8148
|
-
|
|
8251
|
+
if (opts.onEvent && opts.monitor === "wait") {
|
|
8252
|
+
const poller2 = startEventPoller(existing.serviceUrl, apiKey, existing.sessionId, opts.onEvent, restoreFocus, { claim: false });
|
|
8253
|
+
return {
|
|
8254
|
+
sessionId: existing.sessionId,
|
|
8255
|
+
serviceUrl: existing.serviceUrl,
|
|
8256
|
+
apiKey,
|
|
8257
|
+
opened: false,
|
|
8258
|
+
...poller2
|
|
8259
|
+
};
|
|
8260
|
+
}
|
|
8261
|
+
if (opts.onEvent && opts.monitor === "start" && tryClaimStudioMonitor()) {
|
|
8262
|
+
const poller2 = startEventPoller(existing.serviceUrl, apiKey, existing.sessionId, opts.onEvent, restoreFocus, { claim: true });
|
|
8263
|
+
return {
|
|
8264
|
+
sessionId: existing.sessionId,
|
|
8265
|
+
serviceUrl: existing.serviceUrl,
|
|
8266
|
+
apiKey,
|
|
8267
|
+
opened: false,
|
|
8268
|
+
...poller2
|
|
8269
|
+
};
|
|
8270
|
+
}
|
|
8271
|
+
restoreFocus();
|
|
8149
8272
|
return {
|
|
8150
8273
|
sessionId: existing.sessionId,
|
|
8151
8274
|
serviceUrl: existing.serviceUrl,
|
|
8152
8275
|
apiKey,
|
|
8153
8276
|
opened: false,
|
|
8154
|
-
|
|
8277
|
+
abort: noop,
|
|
8278
|
+
done: Promise.resolve()
|
|
8155
8279
|
};
|
|
8156
8280
|
}
|
|
8157
8281
|
const sessionId = crypto4.randomUUID();
|
|
@@ -8162,7 +8286,7 @@ async function openStudioTo(path15, opts = {}) {
|
|
|
8162
8286
|
initialPath: path15,
|
|
8163
8287
|
clientHeaders: opts.clientHeaders
|
|
8164
8288
|
});
|
|
8165
|
-
const poller = opts.onEvent ? startEventPoller(session.serviceUrl, apiKey, session.sessionId, opts.onEvent, restoreFocus) : { abort: noop, done: Promise.resolve() };
|
|
8289
|
+
const poller = opts.onEvent ? startEventPoller(session.serviceUrl, apiKey, session.sessionId, opts.onEvent, restoreFocus, { claim: opts.monitor !== "wait" }) : { abort: noop, done: Promise.resolve() };
|
|
8166
8290
|
return {
|
|
8167
8291
|
sessionId: session.sessionId,
|
|
8168
8292
|
serviceUrl: session.serviceUrl,
|
|
@@ -8192,13 +8316,13 @@ async function loginViaExistingWindow(args) {
|
|
|
8192
8316
|
}
|
|
8193
8317
|
saveCredentials(loginApiKey);
|
|
8194
8318
|
args.opts.onAuthenticated?.(sessionId);
|
|
8195
|
-
if (args.opts.onEvent) {
|
|
8319
|
+
if (args.opts.onEvent && (args.opts.monitor === "wait" || args.opts.monitor === "start" && tryClaimStudioMonitor())) {
|
|
8196
8320
|
await waitForSessionReady({
|
|
8197
8321
|
serviceUrl,
|
|
8198
8322
|
apiKey: loginApiKey,
|
|
8199
8323
|
sessionId
|
|
8200
8324
|
});
|
|
8201
|
-
const poller = startEventPoller(serviceUrl, loginApiKey, sessionId, args.opts.onEvent, args.restoreFocus);
|
|
8325
|
+
const poller = startEventPoller(serviceUrl, loginApiKey, sessionId, args.opts.onEvent, args.restoreFocus, { claim: args.opts.monitor !== "wait" });
|
|
8202
8326
|
return {
|
|
8203
8327
|
sessionId,
|
|
8204
8328
|
serviceUrl,
|
|
@@ -8262,15 +8386,24 @@ function awaitReauthLogin(args) {
|
|
|
8262
8386
|
});
|
|
8263
8387
|
});
|
|
8264
8388
|
}
|
|
8265
|
-
function startEventPoller(serviceUrl, apiKey, sessionId, onEvent, restoreFocus) {
|
|
8389
|
+
function startEventPoller(serviceUrl, apiKey, sessionId, onEvent, restoreFocus, { claim = true } = {}) {
|
|
8390
|
+
if (claim) {
|
|
8391
|
+
claimStudioMonitor();
|
|
8392
|
+
}
|
|
8266
8393
|
const abortController = new AbortController();
|
|
8267
8394
|
let endedNotified = false;
|
|
8395
|
+
const releaseLock = () => {
|
|
8396
|
+
if (claim) {
|
|
8397
|
+
releaseStudioMonitor();
|
|
8398
|
+
}
|
|
8399
|
+
};
|
|
8268
8400
|
const notifyEnded = (baseEvent) => {
|
|
8269
8401
|
if (!endedNotified) {
|
|
8270
8402
|
endedNotified = true;
|
|
8271
8403
|
restoreFocus();
|
|
8272
8404
|
onEvent({ ...baseEvent, type: "studio:session-ended" });
|
|
8273
8405
|
}
|
|
8406
|
+
releaseLock();
|
|
8274
8407
|
abortController.abort();
|
|
8275
8408
|
};
|
|
8276
8409
|
const arbiter = createWindowCloseArbiter({
|
|
@@ -8309,6 +8442,7 @@ function startEventPoller(serviceUrl, apiKey, sessionId, onEvent, restoreFocus)
|
|
|
8309
8442
|
if (event.type === "studio:not-member") {
|
|
8310
8443
|
restoreFocus();
|
|
8311
8444
|
onEvent(event);
|
|
8445
|
+
releaseLock();
|
|
8312
8446
|
abortController.abort();
|
|
8313
8447
|
return;
|
|
8314
8448
|
}
|
|
@@ -8325,8 +8459,15 @@ function startEventPoller(serviceUrl, apiKey, sessionId, onEvent, restoreFocus)
|
|
|
8325
8459
|
if (!abortController.signal.aborted) {
|
|
8326
8460
|
console.error(`studio agent-event poll terminated: ${err.message}`);
|
|
8327
8461
|
}
|
|
8462
|
+
releaseLock();
|
|
8328
8463
|
});
|
|
8329
|
-
return {
|
|
8464
|
+
return {
|
|
8465
|
+
abort: () => {
|
|
8466
|
+
releaseLock();
|
|
8467
|
+
abortController.abort();
|
|
8468
|
+
},
|
|
8469
|
+
done
|
|
8470
|
+
};
|
|
8330
8471
|
}
|
|
8331
8472
|
|
|
8332
8473
|
// ../bitfab-plugin-lib/dist/commands/login.js
|