@ariobarin/glossa 0.2.0-beta.1 → 0.2.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/README.md +4 -1
- package/dist/app.js +795 -556
- package/package.json +1 -1
package/dist/app.js
CHANGED
|
@@ -10436,10 +10436,10 @@ var require_react_reconciler_development = __commonJS({
|
|
|
10436
10436
|
fiber = fiber.next, id--;
|
|
10437
10437
|
return fiber;
|
|
10438
10438
|
}
|
|
10439
|
-
function copyWithSetImpl(obj,
|
|
10440
|
-
if (index >=
|
|
10441
|
-
var key =
|
|
10442
|
-
updated[key] = copyWithSetImpl(obj[key],
|
|
10439
|
+
function copyWithSetImpl(obj, path14, index, value) {
|
|
10440
|
+
if (index >= path14.length) return value;
|
|
10441
|
+
var key = path14[index], updated = isArrayImpl(obj) ? obj.slice() : assign({}, obj);
|
|
10442
|
+
updated[key] = copyWithSetImpl(obj[key], path14, index + 1, value);
|
|
10443
10443
|
return updated;
|
|
10444
10444
|
}
|
|
10445
10445
|
function copyWithRename(obj, oldPath, newPath) {
|
|
@@ -10466,11 +10466,11 @@ var require_react_reconciler_development = __commonJS({
|
|
|
10466
10466
|
);
|
|
10467
10467
|
return updated;
|
|
10468
10468
|
}
|
|
10469
|
-
function copyWithDeleteImpl(obj,
|
|
10470
|
-
var key =
|
|
10471
|
-
if (index + 1 ===
|
|
10469
|
+
function copyWithDeleteImpl(obj, path14, index) {
|
|
10470
|
+
var key = path14[index], updated = isArrayImpl(obj) ? obj.slice() : assign({}, obj);
|
|
10471
|
+
if (index + 1 === path14.length)
|
|
10472
10472
|
return isArrayImpl(updated) ? updated.splice(key, 1) : delete updated[key], updated;
|
|
10473
|
-
updated[key] = copyWithDeleteImpl(obj[key],
|
|
10473
|
+
updated[key] = copyWithDeleteImpl(obj[key], path14, index + 1);
|
|
10474
10474
|
return updated;
|
|
10475
10475
|
}
|
|
10476
10476
|
function shouldSuspendImpl() {
|
|
@@ -23747,29 +23747,29 @@ var require_react_reconciler_development = __commonJS({
|
|
|
23747
23747
|
var didWarnAboutNestedUpdates = false;
|
|
23748
23748
|
var didWarnAboutFindNodeInStrictMode = {};
|
|
23749
23749
|
var overrideHookState = null, overrideHookStateDeletePath = null, overrideHookStateRenamePath = null, overrideProps = null, overridePropsDeletePath = null, overridePropsRenamePath = null, scheduleUpdate = null, scheduleRetry = null, setErrorHandler = null, setSuspenseHandler = null;
|
|
23750
|
-
overrideHookState = function(fiber, id,
|
|
23750
|
+
overrideHookState = function(fiber, id, path14, value) {
|
|
23751
23751
|
id = findHook(fiber, id);
|
|
23752
|
-
null !== id && (
|
|
23752
|
+
null !== id && (path14 = copyWithSetImpl(id.memoizedState, path14, 0, value), id.memoizedState = path14, id.baseState = path14, fiber.memoizedProps = assign({}, fiber.memoizedProps), path14 = enqueueConcurrentRenderForLane(fiber, 2), null !== path14 && scheduleUpdateOnFiber(path14, fiber, 2));
|
|
23753
23753
|
};
|
|
23754
|
-
overrideHookStateDeletePath = function(fiber, id,
|
|
23754
|
+
overrideHookStateDeletePath = function(fiber, id, path14) {
|
|
23755
23755
|
id = findHook(fiber, id);
|
|
23756
|
-
null !== id && (
|
|
23756
|
+
null !== id && (path14 = copyWithDeleteImpl(id.memoizedState, path14, 0), id.memoizedState = path14, id.baseState = path14, fiber.memoizedProps = assign({}, fiber.memoizedProps), path14 = enqueueConcurrentRenderForLane(fiber, 2), null !== path14 && scheduleUpdateOnFiber(path14, fiber, 2));
|
|
23757
23757
|
};
|
|
23758
23758
|
overrideHookStateRenamePath = function(fiber, id, oldPath, newPath) {
|
|
23759
23759
|
id = findHook(fiber, id);
|
|
23760
23760
|
null !== id && (oldPath = copyWithRename(id.memoizedState, oldPath, newPath), id.memoizedState = oldPath, id.baseState = oldPath, fiber.memoizedProps = assign({}, fiber.memoizedProps), oldPath = enqueueConcurrentRenderForLane(fiber, 2), null !== oldPath && scheduleUpdateOnFiber(oldPath, fiber, 2));
|
|
23761
23761
|
};
|
|
23762
|
-
overrideProps = function(fiber,
|
|
23763
|
-
fiber.pendingProps = copyWithSetImpl(fiber.memoizedProps,
|
|
23762
|
+
overrideProps = function(fiber, path14, value) {
|
|
23763
|
+
fiber.pendingProps = copyWithSetImpl(fiber.memoizedProps, path14, 0, value);
|
|
23764
23764
|
fiber.alternate && (fiber.alternate.pendingProps = fiber.pendingProps);
|
|
23765
|
-
|
|
23766
|
-
null !==
|
|
23765
|
+
path14 = enqueueConcurrentRenderForLane(fiber, 2);
|
|
23766
|
+
null !== path14 && scheduleUpdateOnFiber(path14, fiber, 2);
|
|
23767
23767
|
};
|
|
23768
|
-
overridePropsDeletePath = function(fiber,
|
|
23769
|
-
fiber.pendingProps = copyWithDeleteImpl(fiber.memoizedProps,
|
|
23768
|
+
overridePropsDeletePath = function(fiber, path14) {
|
|
23769
|
+
fiber.pendingProps = copyWithDeleteImpl(fiber.memoizedProps, path14, 0);
|
|
23770
23770
|
fiber.alternate && (fiber.alternate.pendingProps = fiber.pendingProps);
|
|
23771
|
-
|
|
23772
|
-
null !==
|
|
23771
|
+
path14 = enqueueConcurrentRenderForLane(fiber, 2);
|
|
23772
|
+
null !== path14 && scheduleUpdateOnFiber(path14, fiber, 2);
|
|
23773
23773
|
};
|
|
23774
23774
|
overridePropsRenamePath = function(fiber, oldPath, newPath) {
|
|
23775
23775
|
fiber.pendingProps = copyWithRename(
|
|
@@ -27007,21 +27007,6 @@ var SessionExpiredError = class extends Error {
|
|
|
27007
27007
|
this.name = "SessionExpiredError";
|
|
27008
27008
|
}
|
|
27009
27009
|
};
|
|
27010
|
-
function accessTokenSubject(credentials) {
|
|
27011
|
-
try {
|
|
27012
|
-
const parts = credentials.accessToken.split(".");
|
|
27013
|
-
if (parts.length !== 3) throw new Error();
|
|
27014
|
-
const payload = JSON.parse(
|
|
27015
|
-
Buffer.from(parts[1], "base64url").toString("utf8")
|
|
27016
|
-
);
|
|
27017
|
-
if (typeof payload.sub !== "string" || payload.sub.length === 0) {
|
|
27018
|
-
throw new Error();
|
|
27019
|
-
}
|
|
27020
|
-
return payload.sub;
|
|
27021
|
-
} catch {
|
|
27022
|
-
throw new Error("Glossa could not identify the signed-in account.");
|
|
27023
|
-
}
|
|
27024
|
-
}
|
|
27025
27010
|
function sessionExpiredError() {
|
|
27026
27011
|
return new SessionExpiredError();
|
|
27027
27012
|
}
|
|
@@ -28014,10 +27999,10 @@ function mergeDefs(...defs) {
|
|
|
28014
27999
|
function cloneDef(schema) {
|
|
28015
28000
|
return mergeDefs(schema._zod.def);
|
|
28016
28001
|
}
|
|
28017
|
-
function getElementAtPath(obj,
|
|
28018
|
-
if (!
|
|
28002
|
+
function getElementAtPath(obj, path14) {
|
|
28003
|
+
if (!path14)
|
|
28019
28004
|
return obj;
|
|
28020
|
-
return
|
|
28005
|
+
return path14.reduce((acc, key) => acc?.[key], obj);
|
|
28021
28006
|
}
|
|
28022
28007
|
function promiseAllObject(promisesObj) {
|
|
28023
28008
|
const keys = Object.keys(promisesObj);
|
|
@@ -28378,11 +28363,11 @@ function aborted(x, startIndex = 0) {
|
|
|
28378
28363
|
}
|
|
28379
28364
|
return false;
|
|
28380
28365
|
}
|
|
28381
|
-
function prefixIssues(
|
|
28366
|
+
function prefixIssues(path14, issues) {
|
|
28382
28367
|
return issues.map((iss) => {
|
|
28383
28368
|
var _a;
|
|
28384
28369
|
(_a = iss).path ?? (_a.path = []);
|
|
28385
|
-
iss.path.unshift(
|
|
28370
|
+
iss.path.unshift(path14);
|
|
28386
28371
|
return iss;
|
|
28387
28372
|
});
|
|
28388
28373
|
}
|
|
@@ -28544,7 +28529,7 @@ function formatError(error46, mapper = (issue2) => issue2.message) {
|
|
|
28544
28529
|
}
|
|
28545
28530
|
function treeifyError(error46, mapper = (issue2) => issue2.message) {
|
|
28546
28531
|
const result = { errors: [] };
|
|
28547
|
-
const processError = (error47,
|
|
28532
|
+
const processError = (error47, path14 = []) => {
|
|
28548
28533
|
var _a, _b;
|
|
28549
28534
|
for (const issue2 of error47.issues) {
|
|
28550
28535
|
if (issue2.code === "invalid_union" && issue2.errors.length) {
|
|
@@ -28554,7 +28539,7 @@ function treeifyError(error46, mapper = (issue2) => issue2.message) {
|
|
|
28554
28539
|
} else if (issue2.code === "invalid_element") {
|
|
28555
28540
|
processError({ issues: issue2.issues }, issue2.path);
|
|
28556
28541
|
} else {
|
|
28557
|
-
const fullpath = [...
|
|
28542
|
+
const fullpath = [...path14, ...issue2.path];
|
|
28558
28543
|
if (fullpath.length === 0) {
|
|
28559
28544
|
result.errors.push(mapper(issue2));
|
|
28560
28545
|
continue;
|
|
@@ -28586,8 +28571,8 @@ function treeifyError(error46, mapper = (issue2) => issue2.message) {
|
|
|
28586
28571
|
}
|
|
28587
28572
|
function toDotPath(_path) {
|
|
28588
28573
|
const segs = [];
|
|
28589
|
-
const
|
|
28590
|
-
for (const seg of
|
|
28574
|
+
const path14 = _path.map((seg) => typeof seg === "object" ? seg.key : seg);
|
|
28575
|
+
for (const seg of path14) {
|
|
28591
28576
|
if (typeof seg === "number")
|
|
28592
28577
|
segs.push(`[${seg}]`);
|
|
28593
28578
|
else if (typeof seg === "symbol")
|
|
@@ -40271,6 +40256,10 @@ function parseInvocation(args) {
|
|
|
40271
40256
|
noOptions("Logout", options);
|
|
40272
40257
|
return { command };
|
|
40273
40258
|
}
|
|
40259
|
+
if (command === "unpair") {
|
|
40260
|
+
noOptions("Unpair", options);
|
|
40261
|
+
return { command };
|
|
40262
|
+
}
|
|
40274
40263
|
if (command === "update") return parseUpdate(options);
|
|
40275
40264
|
return parseWorkspace(args);
|
|
40276
40265
|
}
|
|
@@ -40322,7 +40311,7 @@ async function logoutFromGlossa(dependencies = {}) {
|
|
|
40322
40311
|
const issuer = dependencies.issuer ?? stored?.credentials.issuer;
|
|
40323
40312
|
await remove();
|
|
40324
40313
|
log(
|
|
40325
|
-
present ? "Signed out of Glossa." : "Already signed out of Glossa."
|
|
40314
|
+
present ? "Signed out of the Glossa account. Computer pairing stays active." : "Already signed out of the Glossa account. Computer pairing stays active."
|
|
40326
40315
|
);
|
|
40327
40316
|
const url2 = browserLogoutUrl(issuer ?? loadAuthConfig().issuer);
|
|
40328
40317
|
const opened = await browse(url2);
|
|
@@ -40381,6 +40370,12 @@ function loadRelayEndpoints(environment = process.env) {
|
|
|
40381
40370
|
function defaultDeviceName() {
|
|
40382
40371
|
return deviceNameSchema.parse(os2.hostname());
|
|
40383
40372
|
}
|
|
40373
|
+
var DevicePairingExpiredError = class extends Error {
|
|
40374
|
+
constructor() {
|
|
40375
|
+
super("The Glossa pairing code expired. Run Glossa again to get a new code.");
|
|
40376
|
+
this.name = "DevicePairingExpiredError";
|
|
40377
|
+
}
|
|
40378
|
+
};
|
|
40384
40379
|
function relayError(status, data) {
|
|
40385
40380
|
if (status === 401) return new Error("Glossa login was rejected. Sign in again when prompted.");
|
|
40386
40381
|
if (status === 403 && data.error === "account_disabled") {
|
|
@@ -40434,55 +40429,79 @@ async function listDevices(endpoints, credentials, fetchRequest = fetch) {
|
|
|
40434
40429
|
if (!response.ok) throw relayError(response.status, data);
|
|
40435
40430
|
return parseDevices2(data.devices);
|
|
40436
40431
|
}
|
|
40437
|
-
async function
|
|
40438
|
-
const
|
|
40439
|
-
|
|
40440
|
-
|
|
40441
|
-
|
|
40432
|
+
async function beginDevicePairing(endpoints, deviceName, fetchRequest = fetch) {
|
|
40433
|
+
const name = deviceNameSchema.parse(deviceName);
|
|
40434
|
+
const response = await fetchRequest(`${endpoints.relayOrigin}/v1/device-pairings`, {
|
|
40435
|
+
method: "POST",
|
|
40436
|
+
headers: { "content-type": "application/json" },
|
|
40437
|
+
body: JSON.stringify({ name, platform: `${process.platform}-${process.arch}` })
|
|
40438
|
+
});
|
|
40439
|
+
let data = {};
|
|
40440
|
+
try {
|
|
40441
|
+
data = await response.json();
|
|
40442
|
+
} catch {
|
|
40443
|
+
}
|
|
40444
|
+
if (!response.ok) {
|
|
40445
|
+
if (response.status === 429) {
|
|
40446
|
+
throw new Error("Glossa pairing is rate limited. Try again later.");
|
|
40447
|
+
}
|
|
40448
|
+
throw new Error(`The Glossa relay returned HTTP ${response.status}.`);
|
|
40449
|
+
}
|
|
40450
|
+
if (typeof data.pairing_id !== "string" || typeof data.user_code !== "string" || typeof data.pairing_secret !== "string" || typeof data.expires_at !== "string" || !Number.isInteger(data.poll_interval_ms) || data.poll_interval_ms < 1e3 || data.poll_interval_ms > 1e4 || !/^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i.test(data.pairing_id) || !/^[A-HJ-NP-Z2-9]{5}-[A-HJ-NP-Z2-9]{5}$/.test(data.user_code) || data.pairing_secret.length < 32 || data.pairing_secret.length > 128 || !Number.isFinite(Date.parse(data.expires_at))) {
|
|
40451
|
+
throw new Error("The Glossa relay returned an invalid pairing response.");
|
|
40452
|
+
}
|
|
40453
|
+
return {
|
|
40454
|
+
pairingId: data.pairing_id,
|
|
40455
|
+
userCode: data.user_code,
|
|
40456
|
+
pairingSecret: data.pairing_secret,
|
|
40457
|
+
expiresAt: data.expires_at,
|
|
40458
|
+
pollIntervalMs: data.poll_interval_ms
|
|
40459
|
+
};
|
|
40442
40460
|
}
|
|
40443
|
-
async function
|
|
40444
|
-
const
|
|
40445
|
-
|
|
40446
|
-
|
|
40447
|
-
const response = await fetchRequest(`${endpoints.relayOrigin}/v1/devices/enroll`, {
|
|
40461
|
+
async function completeDevicePairing(endpoints, pairing, fetchRequest = fetch) {
|
|
40462
|
+
const response = await fetchRequest(
|
|
40463
|
+
`${endpoints.relayOrigin}/v1/device-pairings/${encodeURIComponent(pairing.pairingId)}/complete`,
|
|
40464
|
+
{
|
|
40448
40465
|
method: "POST",
|
|
40449
|
-
headers: {
|
|
40450
|
-
|
|
40451
|
-
"content-type": "application/json"
|
|
40452
|
-
},
|
|
40453
|
-
body: JSON.stringify({ name, platform: `${process.platform}-${process.arch}` })
|
|
40454
|
-
});
|
|
40455
|
-
let data = {};
|
|
40456
|
-
try {
|
|
40457
|
-
data = await response.json();
|
|
40458
|
-
} catch {
|
|
40459
|
-
}
|
|
40460
|
-
if (response.status === 409 && data.error === "device_name_conflict" && attempt < 2) {
|
|
40461
|
-
const activeNames = new Set(
|
|
40462
|
-
(await listDevices(endpoints, credentials, fetchRequest)).filter((device) => device.revokedAt === null).map((device) => device.name)
|
|
40463
|
-
);
|
|
40464
|
-
for (let suffix = 2; ; suffix += 1) {
|
|
40465
|
-
const ending = `-${suffix}`;
|
|
40466
|
-
const candidate = `${baseName.slice(0, 80 - ending.length)}${ending}`;
|
|
40467
|
-
if (!activeNames.has(candidate)) {
|
|
40468
|
-
name = deviceNameSchema.parse(candidate);
|
|
40469
|
-
break;
|
|
40470
|
-
}
|
|
40471
|
-
}
|
|
40472
|
-
continue;
|
|
40473
|
-
}
|
|
40474
|
-
if (!response.ok) throw relayError(response.status, data);
|
|
40475
|
-
if (typeof data.device?.id !== "string" || typeof data.device.name !== "string" || typeof data.device_token !== "string") {
|
|
40476
|
-
throw new Error("The Glossa relay returned an invalid device enrollment response.");
|
|
40466
|
+
headers: { "content-type": "application/json" },
|
|
40467
|
+
body: JSON.stringify({ pairing_secret: pairing.pairingSecret })
|
|
40477
40468
|
}
|
|
40478
|
-
|
|
40479
|
-
|
|
40480
|
-
|
|
40481
|
-
|
|
40482
|
-
|
|
40483
|
-
};
|
|
40469
|
+
);
|
|
40470
|
+
let data = {};
|
|
40471
|
+
try {
|
|
40472
|
+
data = await response.json();
|
|
40473
|
+
} catch {
|
|
40484
40474
|
}
|
|
40485
|
-
|
|
40475
|
+
if (response.status === 202 && data.status === "authorization_pending") {
|
|
40476
|
+
return null;
|
|
40477
|
+
}
|
|
40478
|
+
if (response.status === 410 && data.error === "pairing_expired" || response.status === 404 && data.error === "pairing_not_found") {
|
|
40479
|
+
throw new DevicePairingExpiredError();
|
|
40480
|
+
}
|
|
40481
|
+
if (response.status === 429) {
|
|
40482
|
+
throw new Error("Glossa pairing is rate limited. Try again later.");
|
|
40483
|
+
}
|
|
40484
|
+
if (!response.ok) throw relayError(response.status, data);
|
|
40485
|
+
if (typeof data.device?.id !== "string" || typeof data.device.name !== "string" || typeof data.device_token !== "string") {
|
|
40486
|
+
throw new Error("The Glossa relay returned an invalid device pairing response.");
|
|
40487
|
+
}
|
|
40488
|
+
return {
|
|
40489
|
+
relayOrigin: endpoints.relayOrigin,
|
|
40490
|
+
deviceId: data.device.id,
|
|
40491
|
+
deviceName: data.device.name,
|
|
40492
|
+
token: data.device_token
|
|
40493
|
+
};
|
|
40494
|
+
}
|
|
40495
|
+
async function revokePairedDevice(endpoints, device, fetchRequest = fetch) {
|
|
40496
|
+
const response = await fetchRequest(`${endpoints.relayOrigin}/device`, {
|
|
40497
|
+
method: "DELETE",
|
|
40498
|
+
headers: { authorization: `Device ${device.token}` }
|
|
40499
|
+
});
|
|
40500
|
+
if (response.ok || response.status === 401 || response.status === 404) return;
|
|
40501
|
+
if (response.status === 429) {
|
|
40502
|
+
throw new Error("Glossa device authentication is rate limited. Try again later.");
|
|
40503
|
+
}
|
|
40504
|
+
throw new Error(`The Glossa relay returned HTTP ${response.status}.`);
|
|
40486
40505
|
}
|
|
40487
40506
|
async function revokeDevice(endpoints, credentials, deviceId, fetchRequest = fetch) {
|
|
40488
40507
|
const response = await fetchRequest(`${endpoints.relayOrigin}/v1/devices/${encodeURIComponent(deviceId)}`, {
|
|
@@ -47670,8 +47689,8 @@ function Text({ color, backgroundColor, dimColor = false, bold = false, italic =
|
|
|
47670
47689
|
}
|
|
47671
47690
|
|
|
47672
47691
|
// ../../node_modules/ink/build/components/ErrorOverview.js
|
|
47673
|
-
var cleanupPath = (
|
|
47674
|
-
return
|
|
47692
|
+
var cleanupPath = (path14) => {
|
|
47693
|
+
return path14?.replace(`file://${cwd()}/`, "");
|
|
47675
47694
|
};
|
|
47676
47695
|
var stackUtils = new import_stack_utils.default({
|
|
47677
47696
|
cwd: cwd(),
|
|
@@ -47845,7 +47864,7 @@ function App({ children, stdin, stdout, stderr, writeToStdout, writeToStderr, ex
|
|
|
47845
47864
|
for (const subscriber of animationSubscribersRef.current.values()) {
|
|
47846
47865
|
nextDueTime = Math.min(nextDueTime, subscriber.nextDueTime);
|
|
47847
47866
|
}
|
|
47848
|
-
const
|
|
47867
|
+
const delay6 = Math.max(0, nextDueTime - performance.now());
|
|
47849
47868
|
animationTimerRef.current = setTimeout(() => {
|
|
47850
47869
|
animationTimerRef.current = void 0;
|
|
47851
47870
|
const currentTime = performance.now();
|
|
@@ -47859,7 +47878,7 @@ function App({ children, stdin, stdout, stderr, writeToStdout, writeToStderr, ex
|
|
|
47859
47878
|
subscriber.nextDueTime = subscriber.startTime + elapsedFrames * subscriber.interval;
|
|
47860
47879
|
}
|
|
47861
47880
|
scheduleAnimationTick();
|
|
47862
|
-
},
|
|
47881
|
+
}, delay6);
|
|
47863
47882
|
}, [clearAnimationTimer]);
|
|
47864
47883
|
const animationSubscribe = (0, import_react15.useCallback)((callback, interval) => {
|
|
47865
47884
|
const startTime = performance.now();
|
|
@@ -49799,7 +49818,7 @@ function connectHintStore(directory = configDirectory()) {
|
|
|
49799
49818
|
}
|
|
49800
49819
|
async function announceConnectHint(store3, log) {
|
|
49801
49820
|
if (await store3.exists()) return false;
|
|
49802
|
-
log(`Next:
|
|
49821
|
+
log(`Next: return to ChatGPT and use Glossa. If it is not connected yet, follow ${CONNECT_HINT_URL}.`);
|
|
49803
49822
|
await store3.mark();
|
|
49804
49823
|
return true;
|
|
49805
49824
|
}
|
|
@@ -49845,11 +49864,298 @@ async function deleteDeviceCredential() {
|
|
|
49845
49864
|
await store2.delete();
|
|
49846
49865
|
}
|
|
49847
49866
|
|
|
49867
|
+
// src/device-pairing-lock.ts
|
|
49868
|
+
import { randomUUID as randomUUID2 } from "node:crypto";
|
|
49869
|
+
import { mkdir as mkdir4, readFile as readFile3, rm as rm3, stat, writeFile as writeFile4 } from "node:fs/promises";
|
|
49870
|
+
import path6 from "node:path";
|
|
49871
|
+
import { setTimeout as delay2 } from "node:timers/promises";
|
|
49872
|
+
|
|
49873
|
+
// src/update-lock.ts
|
|
49874
|
+
import { randomUUID } from "node:crypto";
|
|
49875
|
+
import { mkdir as mkdir3, readdir, rm as rm2, writeFile as writeFile3 } from "node:fs/promises";
|
|
49876
|
+
import path5 from "node:path";
|
|
49877
|
+
var UPDATE_SUFFIX = ".update";
|
|
49878
|
+
var SESSION_SUFFIX = ".session";
|
|
49879
|
+
function updateRuntimeDirectory() {
|
|
49880
|
+
return path5.join(configDirectory(), "runtime");
|
|
49881
|
+
}
|
|
49882
|
+
function processIsAlive(pid) {
|
|
49883
|
+
if (!Number.isSafeInteger(pid) || pid <= 0) return false;
|
|
49884
|
+
try {
|
|
49885
|
+
process.kill(pid, 0);
|
|
49886
|
+
return true;
|
|
49887
|
+
} catch (error46) {
|
|
49888
|
+
return error46.code === "EPERM";
|
|
49889
|
+
}
|
|
49890
|
+
}
|
|
49891
|
+
function leasePid(name, suffix) {
|
|
49892
|
+
if (!name.endsWith(suffix)) return null;
|
|
49893
|
+
const separator = name.indexOf("-");
|
|
49894
|
+
if (separator <= 0) return null;
|
|
49895
|
+
const pid = Number(name.slice(0, separator));
|
|
49896
|
+
return Number.isSafeInteger(pid) && pid > 0 ? pid : null;
|
|
49897
|
+
}
|
|
49898
|
+
async function activeLeaseFiles(directory, suffix) {
|
|
49899
|
+
let entries;
|
|
49900
|
+
try {
|
|
49901
|
+
entries = await readdir(directory, { withFileTypes: true });
|
|
49902
|
+
} catch (error46) {
|
|
49903
|
+
if (error46.code === "ENOENT") return [];
|
|
49904
|
+
throw error46;
|
|
49905
|
+
}
|
|
49906
|
+
const active = [];
|
|
49907
|
+
for (const entry of entries) {
|
|
49908
|
+
if (!entry.isFile() || !entry.name.endsWith(suffix)) continue;
|
|
49909
|
+
const pid = leasePid(entry.name, suffix);
|
|
49910
|
+
const file2 = path5.join(directory, entry.name);
|
|
49911
|
+
if (pid !== null && processIsAlive(pid)) active.push(file2);
|
|
49912
|
+
else await rm2(file2, { force: true });
|
|
49913
|
+
}
|
|
49914
|
+
return active;
|
|
49915
|
+
}
|
|
49916
|
+
async function updateLockIsActive(directory) {
|
|
49917
|
+
return (await activeLeaseFiles(directory, UPDATE_SUFFIX)).length > 0;
|
|
49918
|
+
}
|
|
49919
|
+
async function activeSessionFiles(directory) {
|
|
49920
|
+
return await activeLeaseFiles(directory, SESSION_SUFFIX);
|
|
49921
|
+
}
|
|
49922
|
+
async function createUpdateLock(directory) {
|
|
49923
|
+
await mkdir3(directory, { recursive: true, mode: 448 });
|
|
49924
|
+
const lockFile = path5.join(
|
|
49925
|
+
directory,
|
|
49926
|
+
`${process.pid}-${randomUUID()}${UPDATE_SUFFIX}`
|
|
49927
|
+
);
|
|
49928
|
+
const content = `${JSON.stringify({ pid: process.pid, startedAt: (/* @__PURE__ */ new Date()).toISOString() })}
|
|
49929
|
+
`;
|
|
49930
|
+
await writeFile3(lockFile, content, {
|
|
49931
|
+
encoding: "utf8",
|
|
49932
|
+
flag: "wx",
|
|
49933
|
+
mode: 384
|
|
49934
|
+
});
|
|
49935
|
+
try {
|
|
49936
|
+
const contenders = await activeLeaseFiles(directory, UPDATE_SUFFIX);
|
|
49937
|
+
if (contenders.some((file2) => file2 !== lockFile)) {
|
|
49938
|
+
throw new Error("Another Glossa update is already running.");
|
|
49939
|
+
}
|
|
49940
|
+
return lockFile;
|
|
49941
|
+
} catch (error46) {
|
|
49942
|
+
await rm2(lockFile, { force: true });
|
|
49943
|
+
throw error46;
|
|
49944
|
+
}
|
|
49945
|
+
}
|
|
49946
|
+
async function withUpdateLease(action, directory = updateRuntimeDirectory()) {
|
|
49947
|
+
const lockFile = await createUpdateLock(directory);
|
|
49948
|
+
try {
|
|
49949
|
+
if ((await activeSessionFiles(directory)).length > 0) {
|
|
49950
|
+
throw new Error("Disconnect every running Glossa workspace before updating.");
|
|
49951
|
+
}
|
|
49952
|
+
return await action();
|
|
49953
|
+
} finally {
|
|
49954
|
+
await rm2(lockFile, { force: true });
|
|
49955
|
+
}
|
|
49956
|
+
}
|
|
49957
|
+
async function withWorkspaceLease(action, directory = updateRuntimeDirectory()) {
|
|
49958
|
+
await mkdir3(directory, { recursive: true, mode: 448 });
|
|
49959
|
+
if (await updateLockIsActive(directory)) {
|
|
49960
|
+
throw new Error("Glossa is updating. Run this workspace again after the update finishes.");
|
|
49961
|
+
}
|
|
49962
|
+
const leaseFile = path5.join(
|
|
49963
|
+
directory,
|
|
49964
|
+
`${process.pid}-${randomUUID()}${SESSION_SUFFIX}`
|
|
49965
|
+
);
|
|
49966
|
+
await writeFile3(
|
|
49967
|
+
leaseFile,
|
|
49968
|
+
`${JSON.stringify({ pid: process.pid, startedAt: (/* @__PURE__ */ new Date()).toISOString() })}
|
|
49969
|
+
`,
|
|
49970
|
+
{ encoding: "utf8", flag: "wx", mode: 384 }
|
|
49971
|
+
);
|
|
49972
|
+
try {
|
|
49973
|
+
if (await updateLockIsActive(directory)) {
|
|
49974
|
+
throw new Error("Glossa is updating. Run this workspace again after the update finishes.");
|
|
49975
|
+
}
|
|
49976
|
+
return await action();
|
|
49977
|
+
} finally {
|
|
49978
|
+
await rm2(leaseFile, { force: true });
|
|
49979
|
+
}
|
|
49980
|
+
}
|
|
49981
|
+
|
|
49982
|
+
// src/device-pairing-lock.ts
|
|
49983
|
+
var DEVICE_PAIRING_LOCK = "device-pairing.lock";
|
|
49984
|
+
var DEVICE_PAIRING_LOCK_GUARD = "device-pairing.lock.guard";
|
|
49985
|
+
var DEVICE_PAIRING_LOCK_POLL_MS = 100;
|
|
49986
|
+
var DEVICE_PAIRING_LOCK_MAX_AGE_MS = 6 * 6e4;
|
|
49987
|
+
var DEVICE_PAIRING_GUARD_MAX_AGE_MS = 3e4;
|
|
49988
|
+
function lockPath(directory) {
|
|
49989
|
+
return path6.join(directory, DEVICE_PAIRING_LOCK);
|
|
49990
|
+
}
|
|
49991
|
+
function guardPath(directory) {
|
|
49992
|
+
return path6.join(directory, DEVICE_PAIRING_LOCK_GUARD);
|
|
49993
|
+
}
|
|
49994
|
+
function newOwner() {
|
|
49995
|
+
return {
|
|
49996
|
+
pid: process.pid,
|
|
49997
|
+
startedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
49998
|
+
token: randomUUID2()
|
|
49999
|
+
};
|
|
50000
|
+
}
|
|
50001
|
+
async function readOwner(file2) {
|
|
50002
|
+
try {
|
|
50003
|
+
const value = JSON.parse(await readFile3(file2, "utf8"));
|
|
50004
|
+
if (!Number.isSafeInteger(value.pid) || typeof value.startedAt !== "string" || !Number.isFinite(Date.parse(value.startedAt)) || typeof value.token !== "string" || value.token.length === 0) {
|
|
50005
|
+
return void 0;
|
|
50006
|
+
}
|
|
50007
|
+
return value;
|
|
50008
|
+
} catch (error46) {
|
|
50009
|
+
if (error46.code === "ENOENT") return void 0;
|
|
50010
|
+
return void 0;
|
|
50011
|
+
}
|
|
50012
|
+
}
|
|
50013
|
+
async function lockIsStale(file2, maxAgeMs) {
|
|
50014
|
+
const owner = await readOwner(file2);
|
|
50015
|
+
if (!owner) {
|
|
50016
|
+
try {
|
|
50017
|
+
const metadata = await stat(file2);
|
|
50018
|
+
return Date.now() - metadata.mtimeMs > maxAgeMs;
|
|
50019
|
+
} catch (error46) {
|
|
50020
|
+
if (error46.code === "ENOENT") return false;
|
|
50021
|
+
return true;
|
|
50022
|
+
}
|
|
50023
|
+
}
|
|
50024
|
+
const ageMs = Date.now() - Date.parse(owner.startedAt);
|
|
50025
|
+
return ageMs > maxAgeMs || !processIsAlive(owner.pid);
|
|
50026
|
+
}
|
|
50027
|
+
async function acquireGuard(directory, signal) {
|
|
50028
|
+
const file2 = guardPath(directory);
|
|
50029
|
+
for (; ; ) {
|
|
50030
|
+
signal?.throwIfAborted();
|
|
50031
|
+
const owner = newOwner();
|
|
50032
|
+
try {
|
|
50033
|
+
await writeFile4(file2, `${JSON.stringify(owner)}
|
|
50034
|
+
`, {
|
|
50035
|
+
encoding: "utf8",
|
|
50036
|
+
flag: "wx",
|
|
50037
|
+
mode: 384
|
|
50038
|
+
});
|
|
50039
|
+
return { file: file2, token: owner.token };
|
|
50040
|
+
} catch (error46) {
|
|
50041
|
+
if (error46.code !== "EEXIST") throw error46;
|
|
50042
|
+
}
|
|
50043
|
+
if (await lockIsStale(file2, DEVICE_PAIRING_GUARD_MAX_AGE_MS)) {
|
|
50044
|
+
await rm3(file2, { force: true });
|
|
50045
|
+
continue;
|
|
50046
|
+
}
|
|
50047
|
+
await delay2(
|
|
50048
|
+
DEVICE_PAIRING_LOCK_POLL_MS,
|
|
50049
|
+
void 0,
|
|
50050
|
+
signal ? { signal } : void 0
|
|
50051
|
+
);
|
|
50052
|
+
}
|
|
50053
|
+
}
|
|
50054
|
+
async function removeOwnedLock(file2, token) {
|
|
50055
|
+
const owner = await readOwner(file2);
|
|
50056
|
+
if (owner?.token === token) await rm3(file2, { force: true });
|
|
50057
|
+
}
|
|
50058
|
+
async function withGuard(directory, action, signal) {
|
|
50059
|
+
const guard = await acquireGuard(directory, signal);
|
|
50060
|
+
try {
|
|
50061
|
+
return await action();
|
|
50062
|
+
} finally {
|
|
50063
|
+
await removeOwnedLock(guard.file, guard.token);
|
|
50064
|
+
}
|
|
50065
|
+
}
|
|
50066
|
+
async function acquireDevicePairingLock(directory, signal) {
|
|
50067
|
+
await mkdir4(directory, { recursive: true, mode: 448 });
|
|
50068
|
+
const file2 = lockPath(directory);
|
|
50069
|
+
for (; ; ) {
|
|
50070
|
+
signal?.throwIfAborted();
|
|
50071
|
+
const owner = newOwner();
|
|
50072
|
+
try {
|
|
50073
|
+
await writeFile4(file2, `${JSON.stringify(owner)}
|
|
50074
|
+
`, {
|
|
50075
|
+
encoding: "utf8",
|
|
50076
|
+
flag: "wx",
|
|
50077
|
+
mode: 384
|
|
50078
|
+
});
|
|
50079
|
+
return { file: file2, token: owner.token };
|
|
50080
|
+
} catch (error46) {
|
|
50081
|
+
if (error46.code !== "EEXIST") throw error46;
|
|
50082
|
+
}
|
|
50083
|
+
const reclaimed = await withGuard(
|
|
50084
|
+
directory,
|
|
50085
|
+
async () => {
|
|
50086
|
+
if (!await lockIsStale(file2, DEVICE_PAIRING_LOCK_MAX_AGE_MS)) {
|
|
50087
|
+
return false;
|
|
50088
|
+
}
|
|
50089
|
+
await rm3(file2, { force: true });
|
|
50090
|
+
return true;
|
|
50091
|
+
},
|
|
50092
|
+
signal
|
|
50093
|
+
);
|
|
50094
|
+
if (reclaimed) continue;
|
|
50095
|
+
await delay2(
|
|
50096
|
+
DEVICE_PAIRING_LOCK_POLL_MS,
|
|
50097
|
+
void 0,
|
|
50098
|
+
signal ? { signal } : void 0
|
|
50099
|
+
);
|
|
50100
|
+
}
|
|
50101
|
+
}
|
|
50102
|
+
async function withDevicePairingLease(action, signal, directory = updateRuntimeDirectory()) {
|
|
50103
|
+
const lock = await acquireDevicePairingLock(directory, signal);
|
|
50104
|
+
try {
|
|
50105
|
+
return await action();
|
|
50106
|
+
} finally {
|
|
50107
|
+
await withGuard(directory, async () => await removeOwnedLock(lock.file, lock.token));
|
|
50108
|
+
}
|
|
50109
|
+
}
|
|
50110
|
+
|
|
50111
|
+
// src/device-pairing.ts
|
|
50112
|
+
import { setTimeout as delay3 } from "node:timers/promises";
|
|
50113
|
+
async function defaultWait(milliseconds, signal) {
|
|
50114
|
+
await delay3(milliseconds, void 0, signal ? { signal } : void 0);
|
|
50115
|
+
}
|
|
50116
|
+
function canceledError2() {
|
|
50117
|
+
return new Error("Pairing canceled.");
|
|
50118
|
+
}
|
|
50119
|
+
async function pairDevice(endpoints, signal, dependencies = {}) {
|
|
50120
|
+
const begin = dependencies.beginDevicePairing ?? beginDevicePairing;
|
|
50121
|
+
const complete = dependencies.completeDevicePairing ?? completeDevicePairing;
|
|
50122
|
+
const name = dependencies.defaultDeviceName ?? defaultDeviceName;
|
|
50123
|
+
const wait = dependencies.wait ?? defaultWait;
|
|
50124
|
+
const now = dependencies.now ?? Date.now;
|
|
50125
|
+
const log = dependencies.log ?? console.log;
|
|
50126
|
+
const baseFetch = dependencies.fetch ?? fetch;
|
|
50127
|
+
const fetchRequest = signal ? async (input, init) => await baseFetch(input, { ...init, signal }) : baseFetch;
|
|
50128
|
+
try {
|
|
50129
|
+
signal?.throwIfAborted();
|
|
50130
|
+
const challenge = await begin(endpoints, name(), fetchRequest);
|
|
50131
|
+
log("This computer needs to be paired with Glossa.");
|
|
50132
|
+
log("In ChatGPT, send this message:");
|
|
50133
|
+
log(`@Glossa pair ${challenge.userCode}`);
|
|
50134
|
+
log("Keep this terminal open. The pairing code expires in 5 minutes.");
|
|
50135
|
+
const expiresAt = Date.parse(challenge.expiresAt);
|
|
50136
|
+
while (now() < expiresAt) {
|
|
50137
|
+
signal?.throwIfAborted();
|
|
50138
|
+
await wait(challenge.pollIntervalMs, signal);
|
|
50139
|
+
signal?.throwIfAborted();
|
|
50140
|
+
const paired = await complete(endpoints, challenge, fetchRequest);
|
|
50141
|
+
if (!paired) continue;
|
|
50142
|
+
log(`Paired with Glossa as ${paired.deviceName}.`);
|
|
50143
|
+
return paired;
|
|
50144
|
+
}
|
|
50145
|
+
throw new Error("The Glossa pairing code expired. Run Glossa again to get a new code.");
|
|
50146
|
+
} catch (error46) {
|
|
50147
|
+
if (signal?.aborted || error46 instanceof Error && error46.name === "AbortError") {
|
|
50148
|
+
throw canceledError2();
|
|
50149
|
+
}
|
|
50150
|
+
throw error46;
|
|
50151
|
+
}
|
|
50152
|
+
}
|
|
50153
|
+
|
|
49848
50154
|
// src/worker/command-service.ts
|
|
49849
50155
|
import { spawn as spawn2 } from "node:child_process";
|
|
49850
|
-
import { randomUUID } from "node:crypto";
|
|
50156
|
+
import { randomUUID as randomUUID3 } from "node:crypto";
|
|
49851
50157
|
import { StringDecoder } from "node:string_decoder";
|
|
49852
|
-
import { setTimeout as
|
|
50158
|
+
import { setTimeout as delay4 } from "node:timers/promises";
|
|
49853
50159
|
|
|
49854
50160
|
// src/worker/errors.ts
|
|
49855
50161
|
var WorkerError = class extends Error {
|
|
@@ -49964,7 +50270,7 @@ async function waitForChange(record2, afterSequence, waitMs) {
|
|
|
49964
50270
|
try {
|
|
49965
50271
|
await Promise.race([
|
|
49966
50272
|
change,
|
|
49967
|
-
|
|
50273
|
+
delay4(waitMs, void 0, { signal: waitController.signal })
|
|
49968
50274
|
]);
|
|
49969
50275
|
} finally {
|
|
49970
50276
|
record2.changeWaiters.delete(changed);
|
|
@@ -50135,7 +50441,7 @@ async function terminateProcessTree(child) {
|
|
|
50135
50441
|
} catch {
|
|
50136
50442
|
child.kill("SIGTERM");
|
|
50137
50443
|
}
|
|
50138
|
-
await
|
|
50444
|
+
await delay4(2e3);
|
|
50139
50445
|
if (child.exitCode === null) {
|
|
50140
50446
|
try {
|
|
50141
50447
|
process.kill(-child.pid, "SIGKILL");
|
|
@@ -50228,7 +50534,7 @@ var CommandService = class {
|
|
|
50228
50534
|
const completion = new Promise((resolve) => {
|
|
50229
50535
|
complete = resolve;
|
|
50230
50536
|
});
|
|
50231
|
-
const id =
|
|
50537
|
+
const id = randomUUID3();
|
|
50232
50538
|
const record2 = {
|
|
50233
50539
|
id,
|
|
50234
50540
|
child,
|
|
@@ -50300,7 +50606,7 @@ var CommandService = class {
|
|
|
50300
50606
|
try {
|
|
50301
50607
|
await Promise.race([
|
|
50302
50608
|
record2.completion,
|
|
50303
|
-
|
|
50609
|
+
delay4(waitMs, void 0, { signal: waitController.signal })
|
|
50304
50610
|
]);
|
|
50305
50611
|
} finally {
|
|
50306
50612
|
waitController.abort();
|
|
@@ -50326,7 +50632,7 @@ var CommandService = class {
|
|
|
50326
50632
|
try {
|
|
50327
50633
|
await Promise.race([
|
|
50328
50634
|
record2.completion,
|
|
50329
|
-
|
|
50635
|
+
delay4(waitMs, void 0, { signal: waitController.signal })
|
|
50330
50636
|
]);
|
|
50331
50637
|
} finally {
|
|
50332
50638
|
waitController.abort();
|
|
@@ -50428,29 +50734,29 @@ var CommandService = class {
|
|
|
50428
50734
|
};
|
|
50429
50735
|
|
|
50430
50736
|
// src/worker/file-service.ts
|
|
50431
|
-
import { createHash, randomUUID as
|
|
50737
|
+
import { createHash, randomUUID as randomUUID4 } from "node:crypto";
|
|
50432
50738
|
import {
|
|
50433
50739
|
chmod as chmod2,
|
|
50434
50740
|
link as link2,
|
|
50435
50741
|
lstat as lstat2,
|
|
50436
|
-
mkdir as
|
|
50742
|
+
mkdir as mkdir5,
|
|
50437
50743
|
opendir,
|
|
50438
50744
|
open,
|
|
50439
|
-
readFile as
|
|
50745
|
+
readFile as readFile4,
|
|
50440
50746
|
rename,
|
|
50441
|
-
rm as
|
|
50747
|
+
rm as rm4,
|
|
50442
50748
|
rmdir,
|
|
50443
|
-
stat as
|
|
50444
|
-
writeFile as
|
|
50749
|
+
stat as stat3,
|
|
50750
|
+
writeFile as writeFile5
|
|
50445
50751
|
} from "node:fs/promises";
|
|
50446
|
-
import
|
|
50752
|
+
import path8 from "node:path";
|
|
50447
50753
|
import { performance as performance2 } from "node:perf_hooks";
|
|
50448
50754
|
import { StringDecoder as StringDecoder2 } from "node:string_decoder";
|
|
50449
50755
|
|
|
50450
50756
|
// src/worker/path-policy.ts
|
|
50451
|
-
import { lstat, realpath, stat } from "node:fs/promises";
|
|
50757
|
+
import { lstat, realpath, stat as stat2 } from "node:fs/promises";
|
|
50452
50758
|
import os5 from "node:os";
|
|
50453
|
-
import
|
|
50759
|
+
import path7 from "node:path";
|
|
50454
50760
|
function samePath(left, right) {
|
|
50455
50761
|
return process.platform === "win32" ? left.toLowerCase() === right.toLowerCase() : left === right;
|
|
50456
50762
|
}
|
|
@@ -50462,15 +50768,15 @@ function accountHomeDirectory() {
|
|
|
50462
50768
|
}
|
|
50463
50769
|
}
|
|
50464
50770
|
function isWithin(root, candidate) {
|
|
50465
|
-
const relative =
|
|
50466
|
-
return relative === "" || !relative.startsWith(`..${
|
|
50771
|
+
const relative = path7.relative(root, candidate);
|
|
50772
|
+
return relative === "" || !relative.startsWith(`..${path7.sep}`) && relative !== ".." && !path7.isAbsolute(relative);
|
|
50467
50773
|
}
|
|
50468
50774
|
function validateRelativePath(value) {
|
|
50469
50775
|
if (value.includes("\0")) {
|
|
50470
50776
|
throw new WorkerError("invalid_path", "Paths cannot contain null bytes.");
|
|
50471
50777
|
}
|
|
50472
50778
|
const explicitNativePosixPath = process.platform !== "win32" && value.startsWith("./");
|
|
50473
|
-
if (
|
|
50779
|
+
if (path7.isAbsolute(value) || path7.posix.isAbsolute(value) || !explicitNativePosixPath && path7.win32.isAbsolute(value)) {
|
|
50474
50780
|
throw new WorkerError("absolute_path", "Absolute paths are not allowed.");
|
|
50475
50781
|
}
|
|
50476
50782
|
const segments = explicitNativePosixPath ? value.split(/\/+/).filter(Boolean) : value.split(/[\\/]+/);
|
|
@@ -50480,20 +50786,20 @@ function validateRelativePath(value) {
|
|
|
50480
50786
|
return value === "" ? "." : value;
|
|
50481
50787
|
}
|
|
50482
50788
|
async function canonicalizeRoot(candidate) {
|
|
50483
|
-
const root = await realpath(
|
|
50789
|
+
const root = await realpath(path7.resolve(candidate)).catch((error46) => {
|
|
50484
50790
|
if (error46.code === "ENOENT") {
|
|
50485
50791
|
throw new WorkerError("root_not_found", "The workspace directory does not exist.");
|
|
50486
50792
|
}
|
|
50487
50793
|
throw error46;
|
|
50488
50794
|
});
|
|
50489
|
-
const rootStat = await
|
|
50795
|
+
const rootStat = await stat2(root);
|
|
50490
50796
|
if (!rootStat.isDirectory()) {
|
|
50491
50797
|
throw new WorkerError("root_not_directory", "The exposed root must be a directory.");
|
|
50492
50798
|
}
|
|
50493
|
-
const filesystemRoot =
|
|
50799
|
+
const filesystemRoot = path7.parse(root).root;
|
|
50494
50800
|
const homes = await Promise.all(
|
|
50495
50801
|
[os5.homedir(), accountHomeDirectory()].map(
|
|
50496
|
-
async (home) => await realpath(home).catch(() =>
|
|
50802
|
+
async (home) => await realpath(home).catch(() => path7.resolve(home))
|
|
50497
50803
|
)
|
|
50498
50804
|
);
|
|
50499
50805
|
const isHomeDirectory = homes.some((home) => samePath(root, home));
|
|
@@ -50529,7 +50835,7 @@ var PathPolicy = class _PathPolicy {
|
|
|
50529
50835
|
return canonical;
|
|
50530
50836
|
}
|
|
50531
50837
|
async resolveDiscoveredExisting(candidate) {
|
|
50532
|
-
const lexical =
|
|
50838
|
+
const lexical = path7.resolve(candidate);
|
|
50533
50839
|
if (!isWithin(this.root, lexical)) {
|
|
50534
50840
|
throw new WorkerError("path_escape", "The requested path escapes the exposed root.");
|
|
50535
50841
|
}
|
|
@@ -50547,7 +50853,7 @@ var PathPolicy = class _PathPolicy {
|
|
|
50547
50853
|
}
|
|
50548
50854
|
async resolveWritableFile(relativePath) {
|
|
50549
50855
|
const lexical = this.resolveLexical(relativePath);
|
|
50550
|
-
const parent =
|
|
50856
|
+
const parent = path7.dirname(lexical);
|
|
50551
50857
|
await this.rejectLinkedComponents(parent);
|
|
50552
50858
|
const canonicalParent = await realpath(parent).catch((error46) => {
|
|
50553
50859
|
if (error46.code === "ENOENT") {
|
|
@@ -50558,7 +50864,7 @@ var PathPolicy = class _PathPolicy {
|
|
|
50558
50864
|
if (!isWithin(this.root, canonicalParent)) {
|
|
50559
50865
|
throw new WorkerError("path_escape", "The destination escapes the exposed root.");
|
|
50560
50866
|
}
|
|
50561
|
-
if (!(await
|
|
50867
|
+
if (!(await stat2(canonicalParent)).isDirectory()) {
|
|
50562
50868
|
throw new WorkerError("not_directory", "The destination parent is not a directory.");
|
|
50563
50869
|
}
|
|
50564
50870
|
try {
|
|
@@ -50578,7 +50884,7 @@ var PathPolicy = class _PathPolicy {
|
|
|
50578
50884
|
if (error46 instanceof WorkerError) throw error46;
|
|
50579
50885
|
if (error46.code !== "ENOENT") throw error46;
|
|
50580
50886
|
}
|
|
50581
|
-
return
|
|
50887
|
+
return path7.join(canonicalParent, path7.basename(lexical));
|
|
50582
50888
|
}
|
|
50583
50889
|
async resolveWritableDirectory(relativePath, recursive) {
|
|
50584
50890
|
const lexical = this.resolveLexical(relativePath);
|
|
@@ -50601,7 +50907,7 @@ var PathPolicy = class _PathPolicy {
|
|
|
50601
50907
|
if (error46.code !== "ENOENT") throw error46;
|
|
50602
50908
|
}
|
|
50603
50909
|
if (!recursive) {
|
|
50604
|
-
const parent =
|
|
50910
|
+
const parent = path7.dirname(lexical);
|
|
50605
50911
|
await this.rejectLinkedComponents(parent);
|
|
50606
50912
|
const canonicalParent = await realpath(parent).catch(
|
|
50607
50913
|
(error46) => {
|
|
@@ -50617,15 +50923,15 @@ var PathPolicy = class _PathPolicy {
|
|
|
50617
50923
|
if (!isWithin(this.root, canonicalParent)) {
|
|
50618
50924
|
throw new WorkerError("path_escape", "The destination escapes the exposed root.");
|
|
50619
50925
|
}
|
|
50620
|
-
if (!(await
|
|
50926
|
+
if (!(await stat2(canonicalParent)).isDirectory()) {
|
|
50621
50927
|
throw new WorkerError("not_directory", "The destination parent is not a directory.");
|
|
50622
50928
|
}
|
|
50623
50929
|
return {
|
|
50624
|
-
target:
|
|
50930
|
+
target: path7.join(canonicalParent, path7.basename(lexical)),
|
|
50625
50931
|
exists: false
|
|
50626
50932
|
};
|
|
50627
50933
|
}
|
|
50628
|
-
let existingAncestor =
|
|
50934
|
+
let existingAncestor = path7.dirname(lexical);
|
|
50629
50935
|
while (!samePath(existingAncestor, this.root)) {
|
|
50630
50936
|
try {
|
|
50631
50937
|
await this.rejectLinkedComponents(existingAncestor);
|
|
@@ -50633,13 +50939,13 @@ var PathPolicy = class _PathPolicy {
|
|
|
50633
50939
|
if (!isWithin(this.root, canonicalAncestor)) {
|
|
50634
50940
|
throw new WorkerError("path_escape", "The destination escapes the exposed root.");
|
|
50635
50941
|
}
|
|
50636
|
-
if (!(await
|
|
50942
|
+
if (!(await stat2(canonicalAncestor)).isDirectory()) {
|
|
50637
50943
|
throw new WorkerError("not_directory", "The destination parent is not a directory.");
|
|
50638
50944
|
}
|
|
50639
50945
|
return {
|
|
50640
|
-
target:
|
|
50946
|
+
target: path7.join(
|
|
50641
50947
|
canonicalAncestor,
|
|
50642
|
-
|
|
50948
|
+
path7.relative(existingAncestor, lexical)
|
|
50643
50949
|
),
|
|
50644
50950
|
exists: false
|
|
50645
50951
|
};
|
|
@@ -50647,13 +50953,13 @@ var PathPolicy = class _PathPolicy {
|
|
|
50647
50953
|
if (error46 instanceof WorkerError) throw error46;
|
|
50648
50954
|
if (error46.code !== "ENOENT") throw error46;
|
|
50649
50955
|
}
|
|
50650
|
-
existingAncestor =
|
|
50956
|
+
existingAncestor = path7.dirname(existingAncestor);
|
|
50651
50957
|
}
|
|
50652
50958
|
return { target: lexical, exists: false };
|
|
50653
50959
|
}
|
|
50654
50960
|
async resolveVacantPath(relativePath) {
|
|
50655
50961
|
const lexical = this.resolveLexical(relativePath);
|
|
50656
|
-
const parent =
|
|
50962
|
+
const parent = path7.dirname(lexical);
|
|
50657
50963
|
await this.rejectLinkedComponents(parent);
|
|
50658
50964
|
const canonicalParent = await realpath(parent).catch(
|
|
50659
50965
|
(error46) => {
|
|
@@ -50669,7 +50975,7 @@ var PathPolicy = class _PathPolicy {
|
|
|
50669
50975
|
if (!isWithin(this.root, canonicalParent)) {
|
|
50670
50976
|
throw new WorkerError("path_escape", "The destination escapes the exposed root.");
|
|
50671
50977
|
}
|
|
50672
|
-
if (!(await
|
|
50978
|
+
if (!(await stat2(canonicalParent)).isDirectory()) {
|
|
50673
50979
|
throw new WorkerError("not_directory", "The destination parent is not a directory.");
|
|
50674
50980
|
}
|
|
50675
50981
|
try {
|
|
@@ -50682,11 +50988,11 @@ var PathPolicy = class _PathPolicy {
|
|
|
50682
50988
|
if (error46 instanceof WorkerError) throw error46;
|
|
50683
50989
|
if (error46.code !== "ENOENT") throw error46;
|
|
50684
50990
|
}
|
|
50685
|
-
return
|
|
50991
|
+
return path7.join(canonicalParent, path7.basename(lexical));
|
|
50686
50992
|
}
|
|
50687
50993
|
resolveLexical(relativePath) {
|
|
50688
50994
|
const validated = validateRelativePath(relativePath);
|
|
50689
|
-
const candidate =
|
|
50995
|
+
const candidate = path7.resolve(this.root, validated);
|
|
50690
50996
|
if (!isWithin(this.root, candidate)) {
|
|
50691
50997
|
throw new WorkerError("path_escape", "The requested path escapes the exposed root.");
|
|
50692
50998
|
}
|
|
@@ -50696,11 +51002,11 @@ var PathPolicy = class _PathPolicy {
|
|
|
50696
51002
|
if (!isWithin(this.root, candidate)) {
|
|
50697
51003
|
throw new WorkerError("path_escape", "The requested path escapes the exposed root.");
|
|
50698
51004
|
}
|
|
50699
|
-
const relative =
|
|
51005
|
+
const relative = path7.relative(this.root, candidate);
|
|
50700
51006
|
if (!relative) return;
|
|
50701
51007
|
let current = this.root;
|
|
50702
|
-
for (const segment of relative.split(
|
|
50703
|
-
current =
|
|
51008
|
+
for (const segment of relative.split(path7.sep)) {
|
|
51009
|
+
current = path7.join(current, segment);
|
|
50704
51010
|
try {
|
|
50705
51011
|
const currentStat = await lstat(current);
|
|
50706
51012
|
if (currentStat.isSymbolicLink()) {
|
|
@@ -50723,12 +51029,12 @@ function sha256(content) {
|
|
|
50723
51029
|
return createHash("sha256").update(content).digest("hex");
|
|
50724
51030
|
}
|
|
50725
51031
|
function isPathWithin(root, candidate) {
|
|
50726
|
-
const relative =
|
|
50727
|
-
return relative === "" || !relative.startsWith(`..${
|
|
51032
|
+
const relative = path8.relative(root, candidate);
|
|
51033
|
+
return relative === "" || !relative.startsWith(`..${path8.sep}`) && relative !== ".." && !path8.isAbsolute(relative);
|
|
50728
51034
|
}
|
|
50729
51035
|
var fileWriteTails = /* @__PURE__ */ new Map();
|
|
50730
51036
|
async function withFileWriteLock(target, operation) {
|
|
50731
|
-
const normalized =
|
|
51037
|
+
const normalized = path8.normalize(target);
|
|
50732
51038
|
const key = process.platform === "win32" ? normalized.toLowerCase() : normalized;
|
|
50733
51039
|
const predecessor = fileWriteTails.get(key);
|
|
50734
51040
|
let release;
|
|
@@ -50745,7 +51051,7 @@ async function withFileWriteLock(target, operation) {
|
|
|
50745
51051
|
}
|
|
50746
51052
|
}
|
|
50747
51053
|
async function withFileWriteLocks(targets, operation) {
|
|
50748
|
-
const unique = [...new Set(targets.map((target) =>
|
|
51054
|
+
const unique = [...new Set(targets.map((target) => path8.normalize(target)))].sort(
|
|
50749
51055
|
(left, right) => left.localeCompare(right)
|
|
50750
51056
|
);
|
|
50751
51057
|
const run = async (index) => {
|
|
@@ -50757,7 +51063,7 @@ async function withFileWriteLocks(targets, operation) {
|
|
|
50757
51063
|
async function requireRevision(target, expectedSha256) {
|
|
50758
51064
|
let actual = null;
|
|
50759
51065
|
try {
|
|
50760
|
-
actual = sha256(await
|
|
51066
|
+
actual = sha256(await readFile4(target));
|
|
50761
51067
|
} catch (error46) {
|
|
50762
51068
|
if (error46.code !== "ENOENT") throw error46;
|
|
50763
51069
|
}
|
|
@@ -50781,7 +51087,7 @@ function compareNames(left, right) {
|
|
|
50781
51087
|
return left < right ? -1 : left > right ? 1 : 0;
|
|
50782
51088
|
}
|
|
50783
51089
|
function relativeSortKey(root, target) {
|
|
50784
|
-
const relative =
|
|
51090
|
+
const relative = path8.relative(root, target);
|
|
50785
51091
|
if (!relative) return ".";
|
|
50786
51092
|
return process.platform === "win32" ? relative.replaceAll("\\", "/") : relative;
|
|
50787
51093
|
}
|
|
@@ -51093,7 +51399,7 @@ var FileService = class {
|
|
|
51093
51399
|
}
|
|
51094
51400
|
async #readResolvedText(target, maximumBytes = MAX_TEXT_BYTES) {
|
|
51095
51401
|
const boundedMaximum = Math.min(maximumBytes, MAX_TEXT_BYTES);
|
|
51096
|
-
const targetStat = await
|
|
51402
|
+
const targetStat = await stat3(target);
|
|
51097
51403
|
if (!targetStat.isFile()) {
|
|
51098
51404
|
throw new WorkerError("not_file", "The requested path is not a file.");
|
|
51099
51405
|
}
|
|
@@ -51136,7 +51442,7 @@ var FileService = class {
|
|
|
51136
51442
|
this.policy.resolveExisting(startPath),
|
|
51137
51443
|
deadlineAt
|
|
51138
51444
|
);
|
|
51139
|
-
const startStat = await this.#withinDeadline(
|
|
51445
|
+
const startStat = await this.#withinDeadline(stat3(start), deadlineAt);
|
|
51140
51446
|
if (!startStat.isDirectory()) {
|
|
51141
51447
|
throw new WorkerError("not_directory", "The requested path is not a directory.");
|
|
51142
51448
|
}
|
|
@@ -51165,7 +51471,7 @@ var FileService = class {
|
|
|
51165
51471
|
if (batch.overflow) throw scanLimitError();
|
|
51166
51472
|
scannedEntries += batch.children.length;
|
|
51167
51473
|
for (const child of batch.children) {
|
|
51168
|
-
const target =
|
|
51474
|
+
const target = path8.join(directory, child.name);
|
|
51169
51475
|
heapPush(frontier, {
|
|
51170
51476
|
target,
|
|
51171
51477
|
sortKey: relativeSortKey(this.policy.root, target),
|
|
@@ -51189,7 +51495,7 @@ var FileService = class {
|
|
|
51189
51495
|
throw scanLimitError();
|
|
51190
51496
|
}
|
|
51191
51497
|
scannedEntries += 1;
|
|
51192
|
-
const target =
|
|
51498
|
+
const target = path8.join(directory, child.name);
|
|
51193
51499
|
let childStat;
|
|
51194
51500
|
try {
|
|
51195
51501
|
childStat = await this.#withinDeadline(
|
|
@@ -51341,7 +51647,7 @@ var FileService = class {
|
|
|
51341
51647
|
if (!patterns) return false;
|
|
51342
51648
|
const normalized = relativePath.replaceAll("\\", "/");
|
|
51343
51649
|
try {
|
|
51344
|
-
return patterns.some((pattern) =>
|
|
51650
|
+
return patterns.some((pattern) => path8.posix.matchesGlob(normalized, pattern));
|
|
51345
51651
|
} catch {
|
|
51346
51652
|
throw new WorkerError("invalid_search", "Search glob pattern is invalid.");
|
|
51347
51653
|
}
|
|
@@ -51359,7 +51665,7 @@ var FileService = class {
|
|
|
51359
51665
|
this.policy.resolveExisting(options.path ?? "."),
|
|
51360
51666
|
deadlineAt
|
|
51361
51667
|
);
|
|
51362
|
-
const startStat = await this.#withinDeadline(
|
|
51668
|
+
const startStat = await this.#withinDeadline(stat3(start), deadlineAt);
|
|
51363
51669
|
const matches = [];
|
|
51364
51670
|
let scannedEntries = 0;
|
|
51365
51671
|
let scannedFiles = 0;
|
|
@@ -51370,7 +51676,7 @@ var FileService = class {
|
|
|
51370
51676
|
const searchFile = async (target) => {
|
|
51371
51677
|
const relative = displayPath(this.policy.root, target);
|
|
51372
51678
|
if (extensions && !extensions.some(
|
|
51373
|
-
(extension) =>
|
|
51679
|
+
(extension) => path8.basename(target).toLowerCase().endsWith(extension)
|
|
51374
51680
|
)) {
|
|
51375
51681
|
return false;
|
|
51376
51682
|
}
|
|
@@ -51465,7 +51771,7 @@ var FileService = class {
|
|
|
51465
51771
|
scannedEntries += batch.children.length;
|
|
51466
51772
|
for (const child of batch.children) {
|
|
51467
51773
|
this.#assertBeforeDeadline(deadlineAt);
|
|
51468
|
-
const target =
|
|
51774
|
+
const target = path8.join(directory, child.name);
|
|
51469
51775
|
let targetType;
|
|
51470
51776
|
if (this.#trustDirectoryEntryTypes) {
|
|
51471
51777
|
if (child.isSymbolicLink()) {
|
|
@@ -51613,12 +51919,12 @@ var FileService = class {
|
|
|
51613
51919
|
);
|
|
51614
51920
|
if (current.exists) return { created: false };
|
|
51615
51921
|
try {
|
|
51616
|
-
await
|
|
51922
|
+
await mkdir5(current.target, { recursive });
|
|
51617
51923
|
} catch (error46) {
|
|
51618
51924
|
if (error46.code !== "EEXIST") throw error46;
|
|
51619
51925
|
}
|
|
51620
51926
|
const resolved = await this.policy.resolveExisting(relativePath);
|
|
51621
|
-
if (!(await
|
|
51927
|
+
if (!(await stat3(resolved)).isDirectory()) {
|
|
51622
51928
|
throw new WorkerError("not_directory", "The destination is not a directory.");
|
|
51623
51929
|
}
|
|
51624
51930
|
return { created: true };
|
|
@@ -51650,12 +51956,12 @@ var FileService = class {
|
|
|
51650
51956
|
try {
|
|
51651
51957
|
if (targetStat.isDirectory()) {
|
|
51652
51958
|
if (recursive) {
|
|
51653
|
-
await
|
|
51959
|
+
await rm4(target, { recursive: true, force: false });
|
|
51654
51960
|
} else {
|
|
51655
51961
|
await rmdir(target);
|
|
51656
51962
|
}
|
|
51657
51963
|
} else {
|
|
51658
|
-
await
|
|
51964
|
+
await rm4(target, { force: false });
|
|
51659
51965
|
}
|
|
51660
51966
|
} catch (error46) {
|
|
51661
51967
|
const code = error46.code;
|
|
@@ -51763,7 +52069,7 @@ var FileService = class {
|
|
|
51763
52069
|
return await withFileWriteLock(target, async () => {
|
|
51764
52070
|
let existingMode;
|
|
51765
52071
|
try {
|
|
51766
|
-
const existingStat = await
|
|
52072
|
+
const existingStat = await stat3(target);
|
|
51767
52073
|
if (existingStat.isFile()) existingMode = existingStat.mode & 4095;
|
|
51768
52074
|
} catch (error46) {
|
|
51769
52075
|
if (error46.code !== "ENOENT") throw error46;
|
|
@@ -51775,9 +52081,9 @@ var FileService = class {
|
|
|
51775
52081
|
);
|
|
51776
52082
|
}
|
|
51777
52083
|
if (expectedSha256) await requireRevision(target, expectedSha256);
|
|
51778
|
-
const temporary =
|
|
52084
|
+
const temporary = path8.join(path8.dirname(target), `.glossa-${randomUUID4()}.tmp`);
|
|
51779
52085
|
try {
|
|
51780
|
-
await
|
|
52086
|
+
await writeFile5(temporary, bytes, { flag: "wx", mode: 384 });
|
|
51781
52087
|
target = await this.policy.resolveWritableFile(relativePath);
|
|
51782
52088
|
const tempStat = await lstat2(temporary);
|
|
51783
52089
|
if (!tempStat.isFile() || tempStat.isSymbolicLink()) {
|
|
@@ -51803,7 +52109,7 @@ var FileService = class {
|
|
|
51803
52109
|
}
|
|
51804
52110
|
}
|
|
51805
52111
|
} finally {
|
|
51806
|
-
await
|
|
52112
|
+
await rm4(temporary, { force: true });
|
|
51807
52113
|
}
|
|
51808
52114
|
return { sha256: sha256(bytes), bytes: bytes.byteLength };
|
|
51809
52115
|
});
|
|
@@ -51830,7 +52136,9 @@ function jobInputContainsRestrictedData(job) {
|
|
|
51830
52136
|
return containsRestrictedAuthenticationData({
|
|
51831
52137
|
query: job.query,
|
|
51832
52138
|
path: job.path,
|
|
51833
|
-
extensions: job.extensions
|
|
52139
|
+
extensions: job.extensions,
|
|
52140
|
+
includeGlobs: job.includeGlobs,
|
|
52141
|
+
excludeGlobs: job.excludeGlobs
|
|
51834
52142
|
});
|
|
51835
52143
|
case "read_file_range":
|
|
51836
52144
|
return containsRestrictedAuthenticationData(job.path);
|
|
@@ -52013,7 +52321,7 @@ var LocalWorker = class _LocalWorker {
|
|
|
52013
52321
|
};
|
|
52014
52322
|
|
|
52015
52323
|
// src/worker/remote-worker.ts
|
|
52016
|
-
import { randomUUID as
|
|
52324
|
+
import { randomUUID as randomUUID5 } from "node:crypto";
|
|
52017
52325
|
var WORKER_REQUEST_TIMEOUT_MS = 19e3;
|
|
52018
52326
|
var DEFAULT_RECONNECT_BASE_MS = 500;
|
|
52019
52327
|
var DEFAULT_RECONNECT_MAX_MS = 1e4;
|
|
@@ -52027,6 +52335,14 @@ var DeviceRejectedError = class extends Error {
|
|
|
52027
52335
|
this.name = "DeviceRejectedError";
|
|
52028
52336
|
}
|
|
52029
52337
|
};
|
|
52338
|
+
var RelayAccessProfileUnsupportedError = class extends Error {
|
|
52339
|
+
constructor(profile) {
|
|
52340
|
+
super(
|
|
52341
|
+
`The relay needs an update before it can represent ${profile} access. Update the relay before reconnecting this workspace.`
|
|
52342
|
+
);
|
|
52343
|
+
this.name = "RelayAccessProfileUnsupportedError";
|
|
52344
|
+
}
|
|
52345
|
+
};
|
|
52030
52346
|
var RelayResponseError = class extends Error {
|
|
52031
52347
|
constructor(status) {
|
|
52032
52348
|
super(`The relay returned HTTP ${status}.`);
|
|
@@ -52125,7 +52441,7 @@ var RemoteWorker = class {
|
|
|
52125
52441
|
#reconnectBaseMs;
|
|
52126
52442
|
#reconnectMaxMs;
|
|
52127
52443
|
#heartbeatMs;
|
|
52128
|
-
#workerId =
|
|
52444
|
+
#workerId = randomUUID5();
|
|
52129
52445
|
#onStatus;
|
|
52130
52446
|
constructor(options) {
|
|
52131
52447
|
this.#origin = new URL(options.origin);
|
|
@@ -52166,8 +52482,10 @@ var RemoteWorker = class {
|
|
|
52166
52482
|
await this.#pollGeneration(session);
|
|
52167
52483
|
} catch (error46) {
|
|
52168
52484
|
if (this.#signal.aborted) return;
|
|
52169
|
-
if (error46 instanceof DeviceRejectedError
|
|
52170
|
-
|
|
52485
|
+
if (error46 instanceof DeviceRejectedError || error46 instanceof RelayAccessProfileUnsupportedError) {
|
|
52486
|
+
throw error46;
|
|
52487
|
+
}
|
|
52488
|
+
const delay6 = reconnectDelayMs(
|
|
52171
52489
|
failures,
|
|
52172
52490
|
this.#random,
|
|
52173
52491
|
this.#reconnectBaseMs,
|
|
@@ -52177,10 +52495,10 @@ var RemoteWorker = class {
|
|
|
52177
52495
|
this.#onStatus({
|
|
52178
52496
|
state: "retrying",
|
|
52179
52497
|
error: error46 instanceof Error ? error46 : new Error(String(error46)),
|
|
52180
|
-
retryInMs:
|
|
52498
|
+
retryInMs: delay6
|
|
52181
52499
|
});
|
|
52182
52500
|
try {
|
|
52183
|
-
await this.#sleep(
|
|
52501
|
+
await this.#sleep(delay6, this.#signal);
|
|
52184
52502
|
} catch (sleepError) {
|
|
52185
52503
|
if (this.#signal.aborted) return;
|
|
52186
52504
|
throw sleepError;
|
|
@@ -52224,100 +52542,74 @@ var RemoteWorker = class {
|
|
|
52224
52542
|
workerId: this.#workerId,
|
|
52225
52543
|
capabilities: { commandProgress: true, concurrentJobs: true }
|
|
52226
52544
|
};
|
|
52227
|
-
const
|
|
52228
|
-
|
|
52229
|
-
|
|
52230
|
-
|
|
52231
|
-
|
|
52232
|
-
accessProfile: this.#accessProfile,
|
|
52233
|
-
...this.#workspaceLabel ? { workspaceLabel: this.#workspaceLabel } : {}
|
|
52234
|
-
} : void 0;
|
|
52235
|
-
const attempts = [
|
|
52236
|
-
...preferredProfileBody ? [{ body: preferredProfileBody, legacyRelay: false }] : [],
|
|
52237
|
-
...versionedCurrentBody && this.#workspaceLabel ? [{
|
|
52238
|
-
body: {
|
|
52239
|
-
...versionedCurrentBody,
|
|
52240
|
-
workspaceLabel: this.#workspaceLabel
|
|
52241
|
-
},
|
|
52242
|
-
legacyRelay: false
|
|
52243
|
-
}] : [],
|
|
52244
|
-
...this.#workspaceLabel ? [{
|
|
52245
|
-
body: {
|
|
52246
|
-
...currentBody,
|
|
52247
|
-
workspaceLabel: this.#workspaceLabel
|
|
52248
|
-
},
|
|
52249
|
-
legacyRelay: false
|
|
52250
|
-
}] : [],
|
|
52251
|
-
...versionedCurrentBody ? [{ body: versionedCurrentBody, legacyRelay: false }] : [],
|
|
52252
|
-
{
|
|
52253
|
-
body: currentBody,
|
|
52254
|
-
legacyRelay: false
|
|
52255
|
-
},
|
|
52256
|
-
...versionedMutationBody && this.#workspaceLabel ? [{
|
|
52257
|
-
body: {
|
|
52258
|
-
...versionedMutationBody,
|
|
52259
|
-
workspaceLabel: this.#workspaceLabel
|
|
52260
|
-
},
|
|
52261
|
-
legacyRelay: false
|
|
52262
|
-
}] : [],
|
|
52263
|
-
...this.#workspaceLabel ? [{
|
|
52264
|
-
body: {
|
|
52265
|
-
...mutationBody,
|
|
52266
|
-
workspaceLabel: this.#workspaceLabel
|
|
52267
|
-
},
|
|
52268
|
-
legacyRelay: false
|
|
52269
|
-
}] : [],
|
|
52270
|
-
...versionedMutationBody ? [{ body: versionedMutationBody, legacyRelay: false }] : [],
|
|
52545
|
+
const capabilityBodies = [
|
|
52546
|
+
currentBody,
|
|
52547
|
+
mutationBody,
|
|
52548
|
+
structuredBody,
|
|
52549
|
+
concurrentBody,
|
|
52271
52550
|
{
|
|
52272
|
-
|
|
52273
|
-
|
|
52551
|
+
workerId: this.#workerId,
|
|
52552
|
+
capabilities: { commandProgress: true }
|
|
52274
52553
|
},
|
|
52275
|
-
|
|
52276
|
-
|
|
52277
|
-
|
|
52278
|
-
workspaceLabel: this.#workspaceLabel
|
|
52279
|
-
},
|
|
52280
|
-
legacyRelay: false
|
|
52281
|
-
}] : [],
|
|
52282
|
-
...this.#workspaceLabel ? [{
|
|
52283
|
-
body: {
|
|
52284
|
-
...structuredBody,
|
|
52285
|
-
workspaceLabel: this.#workspaceLabel
|
|
52286
|
-
},
|
|
52287
|
-
legacyRelay: false
|
|
52288
|
-
}] : [],
|
|
52289
|
-
...versionedStructuredBody ? [{ body: versionedStructuredBody, legacyRelay: false }] : [],
|
|
52554
|
+
{ workerId: this.#workerId }
|
|
52555
|
+
];
|
|
52556
|
+
const metadataPhases = [
|
|
52290
52557
|
{
|
|
52291
|
-
|
|
52292
|
-
|
|
52558
|
+
includeVersion: Boolean(this.#workerVersion),
|
|
52559
|
+
includeLabel: Boolean(this.#workspaceLabel)
|
|
52293
52560
|
},
|
|
52294
52561
|
...this.#workspaceLabel ? [{
|
|
52295
|
-
|
|
52296
|
-
|
|
52297
|
-
workspaceLabel: this.#workspaceLabel
|
|
52298
|
-
},
|
|
52299
|
-
legacyRelay: false
|
|
52562
|
+
includeVersion: Boolean(this.#workerVersion),
|
|
52563
|
+
includeLabel: false
|
|
52300
52564
|
}] : [],
|
|
52301
|
-
{
|
|
52302
|
-
|
|
52303
|
-
legacyRelay: false
|
|
52304
|
-
},
|
|
52305
|
-
{
|
|
52306
|
-
body: {
|
|
52307
|
-
workerId: this.#workerId,
|
|
52308
|
-
capabilities: { commandProgress: true }
|
|
52309
|
-
},
|
|
52310
|
-
legacyRelay: false
|
|
52311
|
-
},
|
|
52312
|
-
{ body: { workerId: this.#workerId }, legacyRelay: false },
|
|
52313
|
-
{ body: {}, legacyRelay: true }
|
|
52565
|
+
...this.#workerVersion && this.#workspaceLabel ? [{ includeVersion: false, includeLabel: true }] : [],
|
|
52566
|
+
{ includeVersion: false, includeLabel: false }
|
|
52314
52567
|
];
|
|
52315
|
-
|
|
52568
|
+
const attempts = [];
|
|
52569
|
+
const seenAttempts = /* @__PURE__ */ new Set();
|
|
52570
|
+
const pushAttempt = (base, includeProfile, includeVersion, includeLabel, legacyRelay = false) => {
|
|
52571
|
+
const body = {
|
|
52572
|
+
...base,
|
|
52573
|
+
...includeProfile && this.#accessProfile ? { accessProfile: this.#accessProfile } : {},
|
|
52574
|
+
...includeVersion && this.#workerVersion ? { workerVersion: this.#workerVersion } : {},
|
|
52575
|
+
...includeLabel && this.#workspaceLabel ? { workspaceLabel: this.#workspaceLabel } : {}
|
|
52576
|
+
};
|
|
52577
|
+
const key = JSON.stringify(body);
|
|
52578
|
+
if (seenAttempts.has(key)) return;
|
|
52579
|
+
seenAttempts.add(key);
|
|
52580
|
+
attempts.push({ body, legacyRelay });
|
|
52581
|
+
};
|
|
52582
|
+
if (this.#accessProfile) {
|
|
52583
|
+
for (const metadata of metadataPhases) {
|
|
52584
|
+
for (const body of capabilityBodies) {
|
|
52585
|
+
pushAttempt(
|
|
52586
|
+
body,
|
|
52587
|
+
true,
|
|
52588
|
+
metadata.includeVersion,
|
|
52589
|
+
metadata.includeLabel
|
|
52590
|
+
);
|
|
52591
|
+
}
|
|
52592
|
+
}
|
|
52593
|
+
}
|
|
52594
|
+
if (this.#accessProfile === void 0 || this.#accessProfile === "system") {
|
|
52595
|
+
for (const metadata of metadataPhases) {
|
|
52596
|
+
for (const body of capabilityBodies) {
|
|
52597
|
+
pushAttempt(
|
|
52598
|
+
body,
|
|
52599
|
+
false,
|
|
52600
|
+
metadata.includeVersion,
|
|
52601
|
+
metadata.includeLabel
|
|
52602
|
+
);
|
|
52603
|
+
}
|
|
52604
|
+
}
|
|
52605
|
+
attempts.push({ body: {}, legacyRelay: true });
|
|
52606
|
+
}
|
|
52607
|
+
for (const attempt of attempts) {
|
|
52316
52608
|
let response;
|
|
52317
52609
|
try {
|
|
52318
52610
|
response = await this.#post("/device/register", attempt.body);
|
|
52319
52611
|
} catch (error46) {
|
|
52320
|
-
if (error46 instanceof RelayResponseError && error46.status === 400
|
|
52612
|
+
if (error46 instanceof RelayResponseError && error46.status === 400) {
|
|
52321
52613
|
continue;
|
|
52322
52614
|
}
|
|
52323
52615
|
throw error46;
|
|
@@ -52330,17 +52622,25 @@ var RemoteWorker = class {
|
|
|
52330
52622
|
throw new Error("The relay returned an invalid registration response.");
|
|
52331
52623
|
}
|
|
52332
52624
|
const workerToken = "workerToken" in value ? optionalWorkerToken(value.workerToken) : void 0;
|
|
52333
|
-
|
|
52625
|
+
const accessProfileAccepted = this.#accessProfile === void 0 || this.#accessProfile === "system" || "accessProfile" in value && value.accessProfile === this.#accessProfile;
|
|
52626
|
+
const session = {
|
|
52334
52627
|
generation: value.generation,
|
|
52335
52628
|
legacyRelay: attempt.legacyRelay,
|
|
52336
52629
|
concurrentJobs: !attempt.legacyRelay && supportsCapability(value, "concurrentJobs"),
|
|
52337
52630
|
structuredReads: !attempt.legacyRelay && supportsCapability(value, "structuredReads"),
|
|
52338
52631
|
structuredMutations: !attempt.legacyRelay && supportsCapability(value, "structuredMutations"),
|
|
52339
52632
|
commandOutputRanges: !attempt.legacyRelay && supportsCapability(value, "commandOutputRanges"),
|
|
52340
|
-
accessProfileAccepted
|
|
52633
|
+
accessProfileAccepted,
|
|
52341
52634
|
workspaceLabelAccepted: this.#workspaceLabel === void 0 || "workspaceLabel" in value && value.workspaceLabel === this.#workspaceLabel,
|
|
52342
52635
|
...workerToken ? { workerToken } : {}
|
|
52343
52636
|
};
|
|
52637
|
+
if (!accessProfileAccepted && this.#accessProfile) {
|
|
52638
|
+
throw new RelayAccessProfileUnsupportedError(this.#accessProfile);
|
|
52639
|
+
}
|
|
52640
|
+
return session;
|
|
52641
|
+
}
|
|
52642
|
+
if (this.#accessProfile && this.#accessProfile !== "system") {
|
|
52643
|
+
throw new RelayAccessProfileUnsupportedError(this.#accessProfile);
|
|
52344
52644
|
}
|
|
52345
52645
|
throw new Error("The relay rejected every supported registration shape.");
|
|
52346
52646
|
}
|
|
@@ -52580,10 +52880,10 @@ var RemoteWorker = class {
|
|
|
52580
52880
|
await unregister(`Device ${this.#deviceToken}`);
|
|
52581
52881
|
}
|
|
52582
52882
|
}
|
|
52583
|
-
async #post(
|
|
52883
|
+
async #post(path14, body, workerToken) {
|
|
52584
52884
|
const timeout = AbortSignal.timeout(WORKER_REQUEST_TIMEOUT_MS);
|
|
52585
52885
|
const signal = AbortSignal.any([this.#signal, timeout]);
|
|
52586
|
-
const response = await this.#fetcher(new URL(
|
|
52886
|
+
const response = await this.#fetcher(new URL(path14, this.#origin), {
|
|
52587
52887
|
method: "POST",
|
|
52588
52888
|
headers: {
|
|
52589
52889
|
authorization: workerToken ? `Worker ${workerToken}` : `Device ${this.#deviceToken}`,
|
|
@@ -52704,56 +53004,27 @@ function visibleWorker(worker, options) {
|
|
|
52704
53004
|
}
|
|
52705
53005
|
async function deviceForSession(endpoints, dependencies = {}, signal) {
|
|
52706
53006
|
const loadDevice = dependencies.loadDeviceCredential ?? loadDeviceCredential;
|
|
52707
|
-
const loadLogin = dependencies.loadCredentials ?? loadCredentials;
|
|
52708
|
-
const validate2 = dependencies.validCredentials ?? validCredentials;
|
|
52709
|
-
const subjectFor = dependencies.accessTokenSubject ?? accessTokenSubject;
|
|
52710
53007
|
const removeDevice = dependencies.deleteDeviceCredential ?? deleteDeviceCredential;
|
|
52711
|
-
const enroll = dependencies.enrollDevice ?? enrollDevice;
|
|
52712
53008
|
const saveDevice = dependencies.saveDeviceCredential ?? saveDeviceCredential;
|
|
52713
|
-
const
|
|
52714
|
-
const
|
|
52715
|
-
const
|
|
52716
|
-
const fetchRequest = signal ? async (input, init) => await baseFetch(input, { ...init, signal }) : baseFetch;
|
|
53009
|
+
const pair = dependencies.pairDevice ?? pairDevice;
|
|
53010
|
+
const revoke = dependencies.revokePairedDevice ?? revokePairedDevice;
|
|
53011
|
+
const withPairingLease = dependencies.withDevicePairingLease ?? withDevicePairingLease;
|
|
52717
53012
|
signal?.throwIfAborted();
|
|
52718
53013
|
const stored = await loadDevice();
|
|
52719
|
-
|
|
52720
|
-
|
|
52721
|
-
|
|
52722
|
-
const
|
|
52723
|
-
if (
|
|
52724
|
-
|
|
52725
|
-
|
|
52726
|
-
|
|
52727
|
-
|
|
52728
|
-
|
|
52729
|
-
const
|
|
52730
|
-
|
|
52731
|
-
|
|
52732
|
-
|
|
52733
|
-
await saveDevice(migrated);
|
|
52734
|
-
return migrated;
|
|
52735
|
-
}
|
|
52736
|
-
await removeDevice();
|
|
52737
|
-
}
|
|
52738
|
-
signal?.throwIfAborted();
|
|
52739
|
-
const current = await currentCredentials();
|
|
52740
|
-
const enrolled = await enroll(
|
|
52741
|
-
endpoints,
|
|
52742
|
-
current,
|
|
52743
|
-
name(),
|
|
52744
|
-
fetchRequest
|
|
52745
|
-
);
|
|
52746
|
-
const bound = {
|
|
52747
|
-
...enrolled,
|
|
52748
|
-
accountSubject: subjectFor(current)
|
|
52749
|
-
};
|
|
52750
|
-
await saveDevice(bound);
|
|
52751
|
-
return bound;
|
|
52752
|
-
}
|
|
52753
|
-
async function reenrollRejectedDevice(endpoints, dependencies = {}, signal) {
|
|
52754
|
-
const remove = dependencies.deleteDeviceCredential ?? deleteDeviceCredential;
|
|
52755
|
-
await remove();
|
|
52756
|
-
return await deviceForSession(endpoints, dependencies, signal);
|
|
53014
|
+
if (stored?.relayOrigin === endpoints.relayOrigin) return stored;
|
|
53015
|
+
return await withPairingLease(async () => {
|
|
53016
|
+
signal?.throwIfAborted();
|
|
53017
|
+
const current = await loadDevice();
|
|
53018
|
+
if (current?.relayOrigin === endpoints.relayOrigin) return current;
|
|
53019
|
+
if (current) {
|
|
53020
|
+
await revoke({ relayOrigin: current.relayOrigin }, current);
|
|
53021
|
+
await removeDevice();
|
|
53022
|
+
}
|
|
53023
|
+
signal?.throwIfAborted();
|
|
53024
|
+
const paired = await pair(endpoints, signal);
|
|
53025
|
+
await saveDevice(paired);
|
|
53026
|
+
return paired;
|
|
53027
|
+
}, signal);
|
|
52757
53028
|
}
|
|
52758
53029
|
function retryMessage(retryInMs) {
|
|
52759
53030
|
const seconds = Math.max(1, Math.ceil(retryInMs / 1e3));
|
|
@@ -52865,9 +53136,6 @@ async function connectRemoteWorker(endpoints, device, worker, options, signal, o
|
|
|
52865
53136
|
await connectHintTask;
|
|
52866
53137
|
}
|
|
52867
53138
|
}
|
|
52868
|
-
function shouldRecoverRejectedDevice(error46, recoveredRejectedDevice, connected) {
|
|
52869
|
-
return error46 instanceof DeviceRejectedError && !recoveredRejectedDevice && !connected;
|
|
52870
|
-
}
|
|
52871
53139
|
async function runManagedSession(root, endpoints, options = {}) {
|
|
52872
53140
|
const controller = new AbortController();
|
|
52873
53141
|
const stop = () => controller.abort();
|
|
@@ -52882,9 +53150,9 @@ async function runManagedSession(root, endpoints, options = {}) {
|
|
|
52882
53150
|
try {
|
|
52883
53151
|
const accessProfile = options.accessProfile ?? DEFAULT_WORKER_ACCESS_PROFILE;
|
|
52884
53152
|
const sessionOptions = { ...options, accessProfile };
|
|
52885
|
-
|
|
53153
|
+
const device = options.device ?? await deviceForSession(
|
|
52886
53154
|
endpoints,
|
|
52887
|
-
|
|
53155
|
+
{},
|
|
52888
53156
|
controller.signal
|
|
52889
53157
|
);
|
|
52890
53158
|
controller.signal.throwIfAborted();
|
|
@@ -52905,41 +53173,18 @@ async function runManagedSession(root, endpoints, options = {}) {
|
|
|
52905
53173
|
console.error(accessProfileSummary(accessProfile));
|
|
52906
53174
|
console.error("Press Ctrl+C to disconnect.");
|
|
52907
53175
|
}
|
|
52908
|
-
|
|
52909
|
-
|
|
52910
|
-
|
|
52911
|
-
|
|
52912
|
-
|
|
52913
|
-
|
|
52914
|
-
|
|
52915
|
-
|
|
52916
|
-
sessionOptions,
|
|
52917
|
-
controller.signal,
|
|
52918
|
-
() => {
|
|
52919
|
-
connected = true;
|
|
52920
|
-
}
|
|
52921
|
-
);
|
|
52922
|
-
break;
|
|
52923
|
-
} catch (error46) {
|
|
52924
|
-
if (!shouldRecoverRejectedDevice(
|
|
52925
|
-
error46,
|
|
52926
|
-
recoveredRejectedDevice,
|
|
52927
|
-
connected
|
|
52928
|
-
)) {
|
|
52929
|
-
throw error46;
|
|
52930
|
-
}
|
|
52931
|
-
recoveredRejectedDevice = true;
|
|
52932
|
-
device = await reenrollRejectedDevice(
|
|
52933
|
-
endpoints,
|
|
52934
|
-
options.credentials ? { credentials: options.credentials } : {},
|
|
52935
|
-
controller.signal
|
|
52936
|
-
);
|
|
52937
|
-
}
|
|
52938
|
-
}
|
|
53176
|
+
await connectRemoteWorker(
|
|
53177
|
+
endpoints,
|
|
53178
|
+
device,
|
|
53179
|
+
worker,
|
|
53180
|
+
sessionOptions,
|
|
53181
|
+
controller.signal,
|
|
53182
|
+
() => void 0
|
|
53183
|
+
);
|
|
52939
53184
|
} catch (error46) {
|
|
52940
53185
|
if (error46 instanceof DeviceRejectedError) {
|
|
52941
53186
|
await deleteDeviceCredential();
|
|
52942
|
-
throw new Error("The relay rejected this
|
|
53187
|
+
throw new Error("The relay rejected this paired computer. Run Glossa again to pair it with your account.");
|
|
52943
53188
|
}
|
|
52944
53189
|
throw error46;
|
|
52945
53190
|
} finally {
|
|
@@ -52964,7 +53209,7 @@ function initialHudState(workspace) {
|
|
|
52964
53209
|
message: void 0,
|
|
52965
53210
|
activities: [],
|
|
52966
53211
|
activityPage: 0,
|
|
52967
|
-
view: "
|
|
53212
|
+
view: "workspace",
|
|
52968
53213
|
status: void 0,
|
|
52969
53214
|
deviceSelection: 0,
|
|
52970
53215
|
pendingAccessProfile: void 0,
|
|
@@ -53035,12 +53280,12 @@ function formatByteCount(value) {
|
|
|
53035
53280
|
}
|
|
53036
53281
|
return `${(kibibytes / 1024).toFixed(1)} MiB`;
|
|
53037
53282
|
}
|
|
53038
|
-
function workspacePath(
|
|
53039
|
-
return
|
|
53283
|
+
function workspacePath(path14) {
|
|
53284
|
+
return path14 || ".";
|
|
53040
53285
|
}
|
|
53041
|
-
function pathSummary(
|
|
53286
|
+
function pathSummary(path14, details = []) {
|
|
53042
53287
|
return {
|
|
53043
|
-
target: `path ${quoteActivityInput(
|
|
53288
|
+
target: `path ${quoteActivityInput(path14)}`,
|
|
53044
53289
|
details,
|
|
53045
53290
|
truncation: "middle"
|
|
53046
53291
|
};
|
|
@@ -53239,6 +53484,7 @@ var COLORS = {
|
|
|
53239
53484
|
muted: "#aaa4b5",
|
|
53240
53485
|
purple: "#8054ff",
|
|
53241
53486
|
purpleReadable: "#ad98ff",
|
|
53487
|
+
success: "#65d6a6",
|
|
53242
53488
|
coral: "#ff665f",
|
|
53243
53489
|
line: "#5c556e"
|
|
53244
53490
|
};
|
|
@@ -53246,7 +53492,9 @@ function Text2({ color, ...props }) {
|
|
|
53246
53492
|
return color === void 0 ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Text, { ...props }) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Text, { ...props, color });
|
|
53247
53493
|
}
|
|
53248
53494
|
var ACTIVITY_REFRESH_INTERVAL_MS = 1e4;
|
|
53495
|
+
var ACTIVITY_STATUS_COLUMN_WIDTH = 2;
|
|
53249
53496
|
var ACTIVITY_TOOL_COLUMN_WIDTH = 15;
|
|
53497
|
+
var ACTIVITY_AGE_COLUMN_WIDTH = 10;
|
|
53250
53498
|
var ACTIVITY_PREAMBLE_LINES = 1;
|
|
53251
53499
|
var HudStore = class {
|
|
53252
53500
|
#state;
|
|
@@ -53292,10 +53540,10 @@ function activityAge(updatedAt, working, now) {
|
|
|
53292
53540
|
if (elapsedMs < 864e5) return `${Math.floor(elapsedMs / 36e5)}h ago`;
|
|
53293
53541
|
return `${Math.floor(elapsedMs / 864e5)}d ago`;
|
|
53294
53542
|
}
|
|
53295
|
-
function
|
|
53296
|
-
if (activity.state === "
|
|
53297
|
-
if (activity.state === "
|
|
53298
|
-
return COLORS.
|
|
53543
|
+
function activityStatus(activity) {
|
|
53544
|
+
if (activity.state === "failed") return { symbol: "\xD7", tone: COLORS.coral };
|
|
53545
|
+
if (activity.state === "working") return { symbol: "\u25CB", tone: COLORS.purpleReadable };
|
|
53546
|
+
return { symbol: "\u2713", tone: COLORS.success };
|
|
53299
53547
|
}
|
|
53300
53548
|
function activitySummary(activity) {
|
|
53301
53549
|
return [activity.summary.target, ...activity.summary.details].join(" \xB7 ");
|
|
@@ -53311,31 +53559,28 @@ function adjacentAccessProfile(accessProfile, direction) {
|
|
|
53311
53559
|
const index = ACCESS_PROFILES.indexOf(accessProfile);
|
|
53312
53560
|
return ACCESS_PROFILES[index + direction];
|
|
53313
53561
|
}
|
|
53314
|
-
function
|
|
53315
|
-
|
|
53316
|
-
|
|
53317
|
-
|
|
53318
|
-
|
|
53319
|
-
|
|
53320
|
-
|
|
53321
|
-
|
|
53322
|
-
|
|
53323
|
-
|
|
53324
|
-
|
|
53325
|
-
}
|
|
53326
|
-
function
|
|
53327
|
-
|
|
53328
|
-
|
|
53329
|
-
|
|
53330
|
-
}
|
|
53331
|
-
|
|
53332
|
-
|
|
53333
|
-
|
|
53334
|
-
|
|
53335
|
-
|
|
53336
|
-
{ key: "L", label: "Sign out", tone: COLORS.coral },
|
|
53337
|
-
{ key: "Q", label: "Quit", tone: COLORS.coral }
|
|
53338
|
-
];
|
|
53562
|
+
function accessProfileCapabilities(accessProfile) {
|
|
53563
|
+
if (accessProfile === "read-only") {
|
|
53564
|
+
return { summary: "Read files only", detail: "No changes \xB7 no commands" };
|
|
53565
|
+
}
|
|
53566
|
+
if (accessProfile === "workspace") {
|
|
53567
|
+
return { summary: "Read + write files", detail: "Commands disabled" };
|
|
53568
|
+
}
|
|
53569
|
+
return {
|
|
53570
|
+
summary: "Read + write files + commands",
|
|
53571
|
+
detail: "OS account permissions apply"
|
|
53572
|
+
};
|
|
53573
|
+
}
|
|
53574
|
+
function primaryFooterHints() {
|
|
53575
|
+
return [
|
|
53576
|
+
{ key: "A", label: "Activity" },
|
|
53577
|
+
{ key: "W", label: "Workspace" },
|
|
53578
|
+
{ key: "D", label: "Devices" },
|
|
53579
|
+
{ key: "?", label: "Help" },
|
|
53580
|
+
{ key: "L", label: "Account sign out", tone: COLORS.coral },
|
|
53581
|
+
{ key: "Q", label: "Quit", tone: COLORS.coral }
|
|
53582
|
+
];
|
|
53583
|
+
}
|
|
53339
53584
|
function contextualFooterHints(state) {
|
|
53340
53585
|
if (state.view === "activity") {
|
|
53341
53586
|
return [
|
|
@@ -53383,7 +53628,7 @@ function splitFooterHints(hints, usable) {
|
|
|
53383
53628
|
return rows;
|
|
53384
53629
|
}
|
|
53385
53630
|
function buildFooterRows(state, usable) {
|
|
53386
|
-
const rows = splitFooterHints(
|
|
53631
|
+
const rows = splitFooterHints(primaryFooterHints(), usable).map((left) => ({
|
|
53387
53632
|
left,
|
|
53388
53633
|
right: []
|
|
53389
53634
|
}));
|
|
@@ -53404,7 +53649,10 @@ function promptText(state) {
|
|
|
53404
53649
|
if (state.busy) return { message: "Working\u2026" };
|
|
53405
53650
|
if (!state.prompt) return void 0;
|
|
53406
53651
|
if (state.prompt.type === "logout") {
|
|
53407
|
-
return {
|
|
53652
|
+
return {
|
|
53653
|
+
message: "Sign out the CLI account and disconnect? Computer pairing stays active.",
|
|
53654
|
+
choices: "Y confirm N cancel"
|
|
53655
|
+
};
|
|
53408
53656
|
}
|
|
53409
53657
|
if (state.prompt.type === "access-confirm") {
|
|
53410
53658
|
const detail = state.prompt.accessProfile === "system" ? "Commands will inherit this OS account's permissions." : "This allows guarded file writes.";
|
|
@@ -53475,7 +53723,7 @@ function Line({ usable, color }) {
|
|
|
53475
53723
|
}
|
|
53476
53724
|
function Header({ state, usable, bodyBudget, color }) {
|
|
53477
53725
|
const statusColor = state.connection === "error" ? COLORS.coral : COLORS.purpleReadable;
|
|
53478
|
-
const pageTitle = state.view === "activity" ? "
|
|
53726
|
+
const pageTitle = state.view === "activity" ? "Activity" : state.view === "devices" ? "Devices" : state.view === "workspace" ? "Workspace" : state.view === "help" ? "Help" : void 0;
|
|
53479
53727
|
const activityPage = state.view === "activity" ? activityPageInfo(state, bodyBudget) : void 0;
|
|
53480
53728
|
const activityRange = activityPage && activityPage.maxPage > 0 ? ` (${activityPage.rangeStart}-${activityPage.rangeEnd}/${state.activities.length})` : "";
|
|
53481
53729
|
const deviceWindow = state.view === "devices" ? deviceListWindow(state, bodyBudget, usable) : void 0;
|
|
@@ -53501,46 +53749,118 @@ function SectionTitle({ children, color, tone = COLORS.purpleReadable }) {
|
|
|
53501
53749
|
function Blank() {
|
|
53502
53750
|
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Box_default, { height: 1 });
|
|
53503
53751
|
}
|
|
53504
|
-
function
|
|
53752
|
+
function ActivityPreviewHeader({ usable, color }) {
|
|
53753
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Box_default, { width: usable, children: [
|
|
53754
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(Box_default, { flexGrow: 1, flexShrink: 1, children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(SectionTitle, { color, children: "Activity" }) }),
|
|
53755
|
+
usable >= 24 ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Box_default, { marginLeft: 1, flexShrink: 0, children: [
|
|
53756
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(Text2, { bold: true, color: color ? COLORS.purpleReadable : void 0, children: "A" }),
|
|
53757
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(Text2, { color: color ? COLORS.muted : void 0, children: " View all" })
|
|
53758
|
+
] }) : null
|
|
53759
|
+
] });
|
|
53760
|
+
}
|
|
53761
|
+
function AccessSectionTitle({ accessProfile, usable, color }) {
|
|
53762
|
+
const lower = adjacentAccessProfile(accessProfile, -1);
|
|
53763
|
+
const higher = adjacentAccessProfile(accessProfile, 1);
|
|
53764
|
+
const key = lower && higher ? "\u2190/\u2192" : lower ? "\u2190" : "\u2192";
|
|
53765
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Box_default, { width: usable, children: [
|
|
53766
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(SectionTitle, { color, children: "Access" }),
|
|
53767
|
+
usable >= 24 ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Box_default, { marginLeft: 3, flexShrink: 0, children: [
|
|
53768
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(Text2, { bold: true, color: color ? COLORS.purpleReadable : void 0, children: key }),
|
|
53769
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(Text2, { color: color ? COLORS.muted : void 0, children: " Switch" })
|
|
53770
|
+
] }) : null
|
|
53771
|
+
] });
|
|
53772
|
+
}
|
|
53773
|
+
function activityLayout(usable) {
|
|
53774
|
+
const toolWidth = Math.min(
|
|
53775
|
+
ACTIVITY_TOOL_COLUMN_WIDTH,
|
|
53776
|
+
Math.max(0, usable - ACTIVITY_STATUS_COLUMN_WIDTH)
|
|
53777
|
+
);
|
|
53778
|
+
return {
|
|
53779
|
+
toolWidth,
|
|
53780
|
+
showSummary: usable >= ACTIVITY_STATUS_COLUMN_WIDTH + toolWidth + 5,
|
|
53781
|
+
showAge: usable >= ACTIVITY_STATUS_COLUMN_WIDTH + toolWidth + ACTIVITY_AGE_COLUMN_WIDTH + 10
|
|
53782
|
+
};
|
|
53783
|
+
}
|
|
53784
|
+
function WorkspaceView({ state, usable, bodyBudget, color, now }) {
|
|
53505
53785
|
const displayedAccessProfile = state.pendingAccessProfile ?? state.accessProfile;
|
|
53786
|
+
const showConnectionMessage = Boolean(
|
|
53787
|
+
state.message && (state.connection === "retrying" || state.connection === "error")
|
|
53788
|
+
);
|
|
53789
|
+
let fixedLines = 3;
|
|
53790
|
+
if (displayedAccessProfile) fixedLines += 5;
|
|
53791
|
+
if (state.deviceName) fixedLines += 3;
|
|
53792
|
+
if (showConnectionMessage) fixedLines += 2;
|
|
53793
|
+
const activityCapacity = Math.min(3, Math.max(0, bodyBudget - fixedLines - 2));
|
|
53794
|
+
const activityPreview = activityCapacity > 0 ? state.activities.slice(-activityCapacity) : [];
|
|
53506
53795
|
return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Box_default, { height: bodyBudget, flexDirection: "column", flexShrink: 0, overflow: "hidden", children: [
|
|
53507
53796
|
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(Blank, {}),
|
|
53508
53797
|
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(SectionTitle, { color, children: "Workspace" }),
|
|
53509
53798
|
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(Text2, { color: color ? COLORS.ink : void 0, wrap: "truncate-middle", children: state.workspace }),
|
|
53510
53799
|
displayedAccessProfile ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [
|
|
53511
53800
|
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(Blank, {}),
|
|
53512
|
-
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
53513
|
-
|
|
53514
|
-
|
|
53801
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
53802
|
+
AccessSectionTitle,
|
|
53803
|
+
{
|
|
53804
|
+
accessProfile: displayedAccessProfile,
|
|
53805
|
+
usable,
|
|
53806
|
+
color
|
|
53807
|
+
}
|
|
53808
|
+
),
|
|
53809
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(Text2, { bold: true, color: color ? COLORS.ink : void 0, wrap: "truncate", children: accessProfileLabel(displayedAccessProfile) }),
|
|
53810
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(Text2, { color: color ? COLORS.muted : void 0, wrap: "truncate", children: accessProfileCapabilities(displayedAccessProfile).summary }),
|
|
53811
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(Text2, { color: color ? COLORS.muted : void 0, wrap: "truncate", children: accessProfileCapabilities(displayedAccessProfile).detail })
|
|
53515
53812
|
] }) : null,
|
|
53516
53813
|
state.deviceName ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [
|
|
53517
53814
|
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(Blank, {}),
|
|
53518
53815
|
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(SectionTitle, { color, children: "Device" }),
|
|
53519
53816
|
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(Text2, { color: color ? COLORS.ink : void 0, wrap: "truncate", children: state.deviceName })
|
|
53520
53817
|
] }) : null,
|
|
53521
|
-
|
|
53818
|
+
activityCapacity > 0 ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [
|
|
53819
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(Blank, {}),
|
|
53820
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(ActivityPreviewHeader, { usable, color }),
|
|
53821
|
+
state.activities.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Text2, { color: color ? COLORS.muted : void 0, children: "No activity yet." }) : activityPreview.map((activity) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
53822
|
+
ActivityRow,
|
|
53823
|
+
{
|
|
53824
|
+
activity,
|
|
53825
|
+
usable,
|
|
53826
|
+
color,
|
|
53827
|
+
now
|
|
53828
|
+
},
|
|
53829
|
+
activity.requestId
|
|
53830
|
+
))
|
|
53831
|
+
] }) : null,
|
|
53832
|
+
showConnectionMessage ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [
|
|
53522
53833
|
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(Blank, {}),
|
|
53523
53834
|
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(Text2, { color: color ? COLORS.coral : void 0, wrap: "truncate", children: state.message })
|
|
53524
53835
|
] }) : null
|
|
53525
53836
|
] });
|
|
53526
53837
|
}
|
|
53527
53838
|
function ActivityRow({ activity, usable, color, now }) {
|
|
53528
|
-
const toolWidth =
|
|
53839
|
+
const { toolWidth, showSummary, showAge } = activityLayout(usable);
|
|
53529
53840
|
const age = activityAge(activity.updatedAt, activity.state === "working", now);
|
|
53530
|
-
const
|
|
53531
|
-
const showSummary = usable >= toolWidth + 5;
|
|
53841
|
+
const status = activityStatus(activity);
|
|
53532
53842
|
return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Box_default, { width: usable, height: 1, flexShrink: 0, children: [
|
|
53843
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(Box_default, { width: ACTIVITY_STATUS_COLUMN_WIDTH, flexShrink: 0, children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Text2, { bold: true, color: color ? status.tone : void 0, children: status.symbol }) }),
|
|
53533
53844
|
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(Box_default, { width: toolWidth, flexShrink: 0, children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
53534
53845
|
Text2,
|
|
53535
53846
|
{
|
|
53536
53847
|
bold: true,
|
|
53537
|
-
color: color ?
|
|
53848
|
+
color: color ? COLORS.ink : void 0,
|
|
53538
53849
|
wrap: "truncate",
|
|
53539
53850
|
children: activity.tool
|
|
53540
53851
|
}
|
|
53541
53852
|
) }),
|
|
53542
53853
|
showSummary ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Box_default, { marginLeft: 2, flexGrow: 1, flexShrink: 1, children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Text2, { color: color ? COLORS.muted : void 0, wrap: "truncate-middle", children: activitySummary(activity) }) }) : null,
|
|
53543
|
-
showAge ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
53854
|
+
showAge ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
53855
|
+
Box_default,
|
|
53856
|
+
{
|
|
53857
|
+
width: ACTIVITY_AGE_COLUMN_WIDTH,
|
|
53858
|
+
marginLeft: 2,
|
|
53859
|
+
flexShrink: 0,
|
|
53860
|
+
justifyContent: "flex-end",
|
|
53861
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Text2, { color: color ? COLORS.muted : void 0, children: age })
|
|
53862
|
+
}
|
|
53863
|
+
) : null
|
|
53544
53864
|
] });
|
|
53545
53865
|
}
|
|
53546
53866
|
function ActivityView({ state, usable, bodyBudget, color, now }) {
|
|
@@ -53675,12 +53995,12 @@ function HelpView({ bodyBudget, color }) {
|
|
|
53675
53995
|
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(HelpRow, { keyLabel: "W", label: "Workspace", color }),
|
|
53676
53996
|
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(HelpRow, { keyLabel: "D", label: "Devices", color }),
|
|
53677
53997
|
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(HelpRow, { keyLabel: "?", label: "Help", color }),
|
|
53678
|
-
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(HelpRow, { keyLabel: "Esc", label: "
|
|
53998
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(HelpRow, { keyLabel: "Esc", label: "Workspace", color }),
|
|
53679
53999
|
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(Blank, {}),
|
|
53680
54000
|
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(SectionTitle, { color, tone: COLORS.coral, children: "Manage" }),
|
|
53681
54001
|
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(HelpRow, { keyLabel: "\u2190/\u2192", label: "Change workspace access", color, tone: COLORS.coral }),
|
|
53682
54002
|
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(HelpRow, { keyLabel: "Enter/R", label: "Revoke selected device", color, tone: COLORS.coral }),
|
|
53683
|
-
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(HelpRow, { keyLabel: "L", label: "
|
|
54003
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(HelpRow, { keyLabel: "L", label: "Account sign out", color, tone: COLORS.coral }),
|
|
53684
54004
|
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(Blank, {}),
|
|
53685
54005
|
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(SectionTitle, { color, children: "App" }),
|
|
53686
54006
|
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(HelpRow, { keyLabel: "Q", label: "Disconnect and quit", color, tone: COLORS.coral }),
|
|
@@ -53754,7 +54074,16 @@ function HudScreen({ state, columns, rows, color = true, now = Date.now() }) {
|
|
|
53754
54074
|
bodyBudget: metrics.bodyBudget,
|
|
53755
54075
|
color
|
|
53756
54076
|
}
|
|
53757
|
-
) : state.view === "workspace" ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
54077
|
+
) : state.view === "workspace" ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
54078
|
+
WorkspaceView,
|
|
54079
|
+
{
|
|
54080
|
+
state,
|
|
54081
|
+
usable: metrics.usable,
|
|
54082
|
+
bodyBudget: metrics.bodyBudget,
|
|
54083
|
+
color,
|
|
54084
|
+
now
|
|
54085
|
+
}
|
|
54086
|
+
) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)(HelpView, { bodyBudget: metrics.bodyBudget, color }),
|
|
53758
54087
|
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(Overlay, { state, usable: metrics.usable, color }),
|
|
53759
54088
|
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(Footer, { rows: metrics.footerRows, usable: metrics.usable, color })
|
|
53760
54089
|
] });
|
|
@@ -53815,7 +54144,7 @@ function HudRuntime({ store: store3, actions, signal, stop }) {
|
|
|
53815
54144
|
const { columns, rows } = use_window_size_default();
|
|
53816
54145
|
const [, setClock] = (0, import_react34.useState)(0);
|
|
53817
54146
|
(0, import_react34.useEffect)(() => {
|
|
53818
|
-
if (state.view !== "activity" || state.activities.length === 0) return;
|
|
54147
|
+
if (state.view !== "activity" && state.view !== "workspace" || state.activities.length === 0) return;
|
|
53819
54148
|
const timer = setInterval(() => setClock((value) => value + 1), ACTIVITY_REFRESH_INTERVAL_MS);
|
|
53820
54149
|
return () => clearInterval(timer);
|
|
53821
54150
|
}, [state.view, state.activities.length]);
|
|
@@ -53927,7 +54256,7 @@ function HudRuntime({ store: store3, actions, signal, stop }) {
|
|
|
53927
54256
|
return;
|
|
53928
54257
|
}
|
|
53929
54258
|
if (key.escape) {
|
|
53930
|
-
store3.update((state2) => ({ ...state2, view: "
|
|
54259
|
+
store3.update((state2) => ({ ...state2, view: "workspace", notice: void 0 }));
|
|
53931
54260
|
return;
|
|
53932
54261
|
}
|
|
53933
54262
|
if (value === "a") {
|
|
@@ -54074,119 +54403,8 @@ async function runSessionHud(actions, input = process.stdin, output = process.st
|
|
|
54074
54403
|
var import_semver = __toESM(require_semver2(), 1);
|
|
54075
54404
|
import { spawn as spawn3 } from "node:child_process";
|
|
54076
54405
|
import { createHash as createHash2 } from "node:crypto";
|
|
54077
|
-
import { chmod as chmod3, readdir as readdir2, rename as rename2, rm as
|
|
54078
|
-
import
|
|
54079
|
-
|
|
54080
|
-
// src/update-lock.ts
|
|
54081
|
-
import { randomUUID as randomUUID4 } from "node:crypto";
|
|
54082
|
-
import { mkdir as mkdir4, readdir, rm as rm3, writeFile as writeFile4 } from "node:fs/promises";
|
|
54083
|
-
import path7 from "node:path";
|
|
54084
|
-
var UPDATE_SUFFIX = ".update";
|
|
54085
|
-
var SESSION_SUFFIX = ".session";
|
|
54086
|
-
function updateRuntimeDirectory() {
|
|
54087
|
-
return path7.join(configDirectory(), "runtime");
|
|
54088
|
-
}
|
|
54089
|
-
function processIsAlive(pid) {
|
|
54090
|
-
if (!Number.isSafeInteger(pid) || pid <= 0) return false;
|
|
54091
|
-
try {
|
|
54092
|
-
process.kill(pid, 0);
|
|
54093
|
-
return true;
|
|
54094
|
-
} catch (error46) {
|
|
54095
|
-
return error46.code === "EPERM";
|
|
54096
|
-
}
|
|
54097
|
-
}
|
|
54098
|
-
function leasePid(name, suffix) {
|
|
54099
|
-
if (!name.endsWith(suffix)) return null;
|
|
54100
|
-
const separator = name.indexOf("-");
|
|
54101
|
-
if (separator <= 0) return null;
|
|
54102
|
-
const pid = Number(name.slice(0, separator));
|
|
54103
|
-
return Number.isSafeInteger(pid) && pid > 0 ? pid : null;
|
|
54104
|
-
}
|
|
54105
|
-
async function activeLeaseFiles(directory, suffix) {
|
|
54106
|
-
let entries;
|
|
54107
|
-
try {
|
|
54108
|
-
entries = await readdir(directory, { withFileTypes: true });
|
|
54109
|
-
} catch (error46) {
|
|
54110
|
-
if (error46.code === "ENOENT") return [];
|
|
54111
|
-
throw error46;
|
|
54112
|
-
}
|
|
54113
|
-
const active = [];
|
|
54114
|
-
for (const entry of entries) {
|
|
54115
|
-
if (!entry.isFile() || !entry.name.endsWith(suffix)) continue;
|
|
54116
|
-
const pid = leasePid(entry.name, suffix);
|
|
54117
|
-
const file2 = path7.join(directory, entry.name);
|
|
54118
|
-
if (pid !== null && processIsAlive(pid)) active.push(file2);
|
|
54119
|
-
else await rm3(file2, { force: true });
|
|
54120
|
-
}
|
|
54121
|
-
return active;
|
|
54122
|
-
}
|
|
54123
|
-
async function updateLockIsActive(directory) {
|
|
54124
|
-
return (await activeLeaseFiles(directory, UPDATE_SUFFIX)).length > 0;
|
|
54125
|
-
}
|
|
54126
|
-
async function activeSessionFiles(directory) {
|
|
54127
|
-
return await activeLeaseFiles(directory, SESSION_SUFFIX);
|
|
54128
|
-
}
|
|
54129
|
-
async function createUpdateLock(directory) {
|
|
54130
|
-
await mkdir4(directory, { recursive: true, mode: 448 });
|
|
54131
|
-
const lockFile = path7.join(
|
|
54132
|
-
directory,
|
|
54133
|
-
`${process.pid}-${randomUUID4()}${UPDATE_SUFFIX}`
|
|
54134
|
-
);
|
|
54135
|
-
const content = `${JSON.stringify({ pid: process.pid, startedAt: (/* @__PURE__ */ new Date()).toISOString() })}
|
|
54136
|
-
`;
|
|
54137
|
-
await writeFile4(lockFile, content, {
|
|
54138
|
-
encoding: "utf8",
|
|
54139
|
-
flag: "wx",
|
|
54140
|
-
mode: 384
|
|
54141
|
-
});
|
|
54142
|
-
try {
|
|
54143
|
-
const contenders = await activeLeaseFiles(directory, UPDATE_SUFFIX);
|
|
54144
|
-
if (contenders.some((file2) => file2 !== lockFile)) {
|
|
54145
|
-
throw new Error("Another Glossa update is already running.");
|
|
54146
|
-
}
|
|
54147
|
-
return lockFile;
|
|
54148
|
-
} catch (error46) {
|
|
54149
|
-
await rm3(lockFile, { force: true });
|
|
54150
|
-
throw error46;
|
|
54151
|
-
}
|
|
54152
|
-
}
|
|
54153
|
-
async function withUpdateLease(action, directory = updateRuntimeDirectory()) {
|
|
54154
|
-
const lockFile = await createUpdateLock(directory);
|
|
54155
|
-
try {
|
|
54156
|
-
if ((await activeSessionFiles(directory)).length > 0) {
|
|
54157
|
-
throw new Error("Disconnect every running Glossa workspace before updating.");
|
|
54158
|
-
}
|
|
54159
|
-
return await action();
|
|
54160
|
-
} finally {
|
|
54161
|
-
await rm3(lockFile, { force: true });
|
|
54162
|
-
}
|
|
54163
|
-
}
|
|
54164
|
-
async function withWorkspaceLease(action, directory = updateRuntimeDirectory()) {
|
|
54165
|
-
await mkdir4(directory, { recursive: true, mode: 448 });
|
|
54166
|
-
if (await updateLockIsActive(directory)) {
|
|
54167
|
-
throw new Error("Glossa is updating. Run this workspace again after the update finishes.");
|
|
54168
|
-
}
|
|
54169
|
-
const leaseFile = path7.join(
|
|
54170
|
-
directory,
|
|
54171
|
-
`${process.pid}-${randomUUID4()}${SESSION_SUFFIX}`
|
|
54172
|
-
);
|
|
54173
|
-
await writeFile4(
|
|
54174
|
-
leaseFile,
|
|
54175
|
-
`${JSON.stringify({ pid: process.pid, startedAt: (/* @__PURE__ */ new Date()).toISOString() })}
|
|
54176
|
-
`,
|
|
54177
|
-
{ encoding: "utf8", flag: "wx", mode: 384 }
|
|
54178
|
-
);
|
|
54179
|
-
try {
|
|
54180
|
-
if (await updateLockIsActive(directory)) {
|
|
54181
|
-
throw new Error("Glossa is updating. Run this workspace again after the update finishes.");
|
|
54182
|
-
}
|
|
54183
|
-
return await action();
|
|
54184
|
-
} finally {
|
|
54185
|
-
await rm3(leaseFile, { force: true });
|
|
54186
|
-
}
|
|
54187
|
-
}
|
|
54188
|
-
|
|
54189
|
-
// src/update-service.ts
|
|
54406
|
+
import { chmod as chmod3, readdir as readdir2, rename as rename2, rm as rm5, writeFile as writeFile6 } from "node:fs/promises";
|
|
54407
|
+
import path9 from "node:path";
|
|
54190
54408
|
var PACKAGE_NAME = "@ariobarin/glossa";
|
|
54191
54409
|
var DEFAULT_REGISTRY_URL = "https://registry.npmjs.org/@ariobarin%2Fglossa";
|
|
54192
54410
|
var DEFAULT_RELEASE_BASE_URL = "https://github.com/ariobarin/glossa/releases/download";
|
|
@@ -54263,8 +54481,8 @@ async function cleanupUpdateBackups(distribution, options = {}) {
|
|
|
54263
54481
|
const platform2 = options.platform ?? process.platform;
|
|
54264
54482
|
if (distribution !== "standalone" || platform2 !== "win32") return;
|
|
54265
54483
|
const executable = options.executablePath ?? process.execPath;
|
|
54266
|
-
const directory =
|
|
54267
|
-
const prefix = `${
|
|
54484
|
+
const directory = path9.dirname(executable);
|
|
54485
|
+
const prefix = `${path9.basename(executable)}.old-`;
|
|
54268
54486
|
let entries;
|
|
54269
54487
|
try {
|
|
54270
54488
|
entries = await readdir2(directory, { withFileTypes: true });
|
|
@@ -54277,7 +54495,7 @@ async function cleanupUpdateBackups(distribution, options = {}) {
|
|
|
54277
54495
|
const ownerPid = backupOwnerPid(entry.name, prefix);
|
|
54278
54496
|
return ownerPid !== null && !processIsAlive(ownerPid);
|
|
54279
54497
|
}).map(async (entry) => {
|
|
54280
|
-
await
|
|
54498
|
+
await rm5(path9.join(directory, entry.name), { force: true }).catch(() => void 0);
|
|
54281
54499
|
})
|
|
54282
54500
|
);
|
|
54283
54501
|
}
|
|
@@ -54353,12 +54571,12 @@ async function installStandaloneUpdate(info, options) {
|
|
|
54353
54571
|
if (actual !== expected) {
|
|
54354
54572
|
throw new Error("Glossa refused to update because the SHA-256 checksum did not match.");
|
|
54355
54573
|
}
|
|
54356
|
-
const download =
|
|
54357
|
-
|
|
54358
|
-
`${
|
|
54574
|
+
const download = path9.join(
|
|
54575
|
+
path9.dirname(destination),
|
|
54576
|
+
`${path9.basename(destination)}.download-${process.pid}`
|
|
54359
54577
|
);
|
|
54360
54578
|
try {
|
|
54361
|
-
await
|
|
54579
|
+
await writeFile6(download, binary, { mode: 493 });
|
|
54362
54580
|
if (platform2 === "win32") {
|
|
54363
54581
|
await replaceWindowsExecutable(download, destination);
|
|
54364
54582
|
} else {
|
|
@@ -54367,7 +54585,7 @@ async function installStandaloneUpdate(info, options) {
|
|
|
54367
54585
|
}
|
|
54368
54586
|
return "installed";
|
|
54369
54587
|
} catch (error46) {
|
|
54370
|
-
await
|
|
54588
|
+
await rm5(download, { force: true });
|
|
54371
54589
|
throw error46;
|
|
54372
54590
|
}
|
|
54373
54591
|
}
|
|
@@ -54381,14 +54599,14 @@ async function installUpdate(info, distribution, options = {}) {
|
|
|
54381
54599
|
}
|
|
54382
54600
|
|
|
54383
54601
|
// src/update-state.ts
|
|
54384
|
-
import { chmod as chmod4, mkdir as
|
|
54385
|
-
import
|
|
54602
|
+
import { chmod as chmod4, mkdir as mkdir6, readFile as readFile5, rename as rename3, rm as rm6, writeFile as writeFile7 } from "node:fs/promises";
|
|
54603
|
+
import path10 from "node:path";
|
|
54386
54604
|
var UPDATE_CHECK_INTERVAL_MS = 24 * 60 * 60 * 1e3;
|
|
54387
54605
|
function defaultUpdateChannel(version2) {
|
|
54388
54606
|
return version2.includes("-") ? "beta" : "stable";
|
|
54389
54607
|
}
|
|
54390
54608
|
function updateStateFile() {
|
|
54391
|
-
return
|
|
54609
|
+
return path10.join(configDirectory(), "updates.json");
|
|
54392
54610
|
}
|
|
54393
54611
|
function isPolicy(value) {
|
|
54394
54612
|
return value === "notify" || value === "auto" || value === "off";
|
|
@@ -54405,7 +54623,7 @@ async function loadUpdateState(currentVersion, file2 = updateStateFile()) {
|
|
|
54405
54623
|
channel: defaultUpdateChannel(currentVersion)
|
|
54406
54624
|
};
|
|
54407
54625
|
try {
|
|
54408
|
-
const parsed = JSON.parse(await
|
|
54626
|
+
const parsed = JSON.parse(await readFile5(file2, "utf8"));
|
|
54409
54627
|
const lastCheckedAt = optionalString(parsed.lastCheckedAt);
|
|
54410
54628
|
const latestVersion = optionalString(parsed.latestVersion);
|
|
54411
54629
|
return {
|
|
@@ -54422,11 +54640,11 @@ async function loadUpdateState(currentVersion, file2 = updateStateFile()) {
|
|
|
54422
54640
|
}
|
|
54423
54641
|
}
|
|
54424
54642
|
async function saveUpdateState(state, file2 = updateStateFile()) {
|
|
54425
|
-
const directory =
|
|
54643
|
+
const directory = path10.dirname(file2);
|
|
54426
54644
|
const temporary = `${file2}.${process.pid}.tmp`;
|
|
54427
|
-
await
|
|
54645
|
+
await mkdir6(directory, { recursive: true, mode: 448 });
|
|
54428
54646
|
try {
|
|
54429
|
-
await
|
|
54647
|
+
await writeFile7(temporary, `${JSON.stringify(state, null, 2)}
|
|
54430
54648
|
`, {
|
|
54431
54649
|
encoding: "utf8",
|
|
54432
54650
|
mode: 384
|
|
@@ -54434,7 +54652,7 @@ async function saveUpdateState(state, file2 = updateStateFile()) {
|
|
|
54434
54652
|
if (process.platform !== "win32") await chmod4(temporary, 384);
|
|
54435
54653
|
await rename3(temporary, file2);
|
|
54436
54654
|
} finally {
|
|
54437
|
-
await
|
|
54655
|
+
await rm6(temporary, { force: true });
|
|
54438
54656
|
}
|
|
54439
54657
|
}
|
|
54440
54658
|
async function configureUpdates(currentVersion, changes, file2 = updateStateFile()) {
|
|
@@ -54461,15 +54679,31 @@ function isUpdateCheckDue(lastCheckedAt, now = Date.now()) {
|
|
|
54461
54679
|
return !Number.isFinite(checkedAt) || now - checkedAt >= UPDATE_CHECK_INTERVAL_MS;
|
|
54462
54680
|
}
|
|
54463
54681
|
|
|
54682
|
+
// src/unpair.ts
|
|
54683
|
+
async function unpairComputer(dependencies = {}) {
|
|
54684
|
+
const load = dependencies.loadDeviceCredential ?? loadDeviceCredential;
|
|
54685
|
+
const remove = dependencies.deleteDeviceCredential ?? deleteDeviceCredential;
|
|
54686
|
+
const revoke = dependencies.revokePairedDevice ?? revokePairedDevice;
|
|
54687
|
+
const log = dependencies.log ?? console.log;
|
|
54688
|
+
const device = await load();
|
|
54689
|
+
if (!device) {
|
|
54690
|
+
log("This computer is not paired with Glossa.");
|
|
54691
|
+
return;
|
|
54692
|
+
}
|
|
54693
|
+
await revoke({ relayOrigin: device.relayOrigin }, device);
|
|
54694
|
+
await remove();
|
|
54695
|
+
log("Unpaired this computer from Glossa.");
|
|
54696
|
+
}
|
|
54697
|
+
|
|
54464
54698
|
// src/usage-store.ts
|
|
54465
|
-
import { appendFile, chmod as chmod5, mkdir as
|
|
54466
|
-
import
|
|
54699
|
+
import { appendFile, chmod as chmod5, mkdir as mkdir7, readFile as readFile6 } from "node:fs/promises";
|
|
54700
|
+
import path11 from "node:path";
|
|
54467
54701
|
function usageFile() {
|
|
54468
|
-
return
|
|
54702
|
+
return path11.join(configDirectory(), "usage.jsonl");
|
|
54469
54703
|
}
|
|
54470
54704
|
async function recordUsageEvent(event, file2 = usageFile()) {
|
|
54471
|
-
const directory =
|
|
54472
|
-
await
|
|
54705
|
+
const directory = path11.dirname(file2);
|
|
54706
|
+
await mkdir7(directory, { recursive: true, mode: 448 });
|
|
54473
54707
|
await appendFile(file2, `${JSON.stringify(event)}
|
|
54474
54708
|
`, {
|
|
54475
54709
|
encoding: "utf8",
|
|
@@ -54487,15 +54721,15 @@ async function recordToolUsage(tool, ok, at = /* @__PURE__ */ new Date(), file2
|
|
|
54487
54721
|
// src/worker/root-selection.ts
|
|
54488
54722
|
import { realpath as realpath2 } from "node:fs/promises";
|
|
54489
54723
|
import os6 from "node:os";
|
|
54490
|
-
import
|
|
54724
|
+
import path12 from "node:path";
|
|
54491
54725
|
function containsPath(root, candidate) {
|
|
54492
|
-
const relative =
|
|
54493
|
-
return relative !== "" && relative !== ".." && !relative.startsWith(`..${
|
|
54726
|
+
const relative = path12.relative(root, candidate);
|
|
54727
|
+
return relative !== "" && relative !== ".." && !relative.startsWith(`..${path12.sep}`) && !path12.isAbsolute(relative);
|
|
54494
54728
|
}
|
|
54495
54729
|
async function rejectImplicitHomeAncestor(root) {
|
|
54496
54730
|
const homes = await Promise.all(
|
|
54497
54731
|
[os6.homedir(), accountHomeDirectory()].map(
|
|
54498
|
-
async (home) => await realpath2(home).catch(() =>
|
|
54732
|
+
async (home) => await realpath2(home).catch(() => path12.resolve(home))
|
|
54499
54733
|
)
|
|
54500
54734
|
);
|
|
54501
54735
|
if (homes.some((home) => containsPath(root, home))) {
|
|
@@ -54513,10 +54747,10 @@ async function selectExposureRoot(explicitPath, cwd2 = process.cwd()) {
|
|
|
54513
54747
|
|
|
54514
54748
|
// src/worker/workspace-lease.ts
|
|
54515
54749
|
import { createHash as createHash3 } from "node:crypto";
|
|
54516
|
-
import { lstat as lstat3, mkdir as
|
|
54750
|
+
import { lstat as lstat3, mkdir as mkdir8, rm as rm7, stat as stat4 } from "node:fs/promises";
|
|
54517
54751
|
import net from "node:net";
|
|
54518
|
-
import
|
|
54519
|
-
import { setTimeout as
|
|
54752
|
+
import path13 from "node:path";
|
|
54753
|
+
import { setTimeout as delay5 } from "node:timers/promises";
|
|
54520
54754
|
var GUARD_STALE_MS = 5e3;
|
|
54521
54755
|
var GUARD_RETRY_MS = 10;
|
|
54522
54756
|
var GUARD_TIMEOUT_MS = 1e4;
|
|
@@ -54539,12 +54773,12 @@ function workspaceIdentity(root) {
|
|
|
54539
54773
|
}
|
|
54540
54774
|
function defaultLeaseDirectory() {
|
|
54541
54775
|
if (typeof process.getuid === "function") {
|
|
54542
|
-
return
|
|
54776
|
+
return path13.join("/tmp", `glossa-workspaces-${process.getuid()}`);
|
|
54543
54777
|
}
|
|
54544
|
-
return
|
|
54778
|
+
return path13.join(accountHomeDirectory(), ".glossa-workspace-leases");
|
|
54545
54779
|
}
|
|
54546
54780
|
async function ensureLeaseDirectory(directory) {
|
|
54547
|
-
await
|
|
54781
|
+
await mkdir8(directory, { recursive: true, mode: 448 });
|
|
54548
54782
|
const directoryStat = await lstat3(directory);
|
|
54549
54783
|
if (directoryStat.isSymbolicLink() || !directoryStat.isDirectory()) {
|
|
54550
54784
|
throw new Error("The Glossa workspace lease location is not a private directory.");
|
|
@@ -54561,26 +54795,26 @@ function endpointFor(identity, directory) {
|
|
|
54561
54795
|
if (process.platform === "win32") {
|
|
54562
54796
|
return `\\\\.\\pipe\\glossa-workspace-${identity}`;
|
|
54563
54797
|
}
|
|
54564
|
-
return
|
|
54798
|
+
return path13.join(directory, `${identity}.sock`);
|
|
54565
54799
|
}
|
|
54566
|
-
async function
|
|
54567
|
-
const guard =
|
|
54800
|
+
async function acquireGuard2(directory, identity) {
|
|
54801
|
+
const guard = path13.join(directory, `${identity}.guard`);
|
|
54568
54802
|
const deadline = Date.now() + GUARD_TIMEOUT_MS;
|
|
54569
54803
|
while (true) {
|
|
54570
54804
|
try {
|
|
54571
|
-
await
|
|
54572
|
-
return async () => await
|
|
54805
|
+
await mkdir8(guard, { mode: 448 });
|
|
54806
|
+
return async () => await rm7(guard, { recursive: true, force: true });
|
|
54573
54807
|
} catch (error46) {
|
|
54574
54808
|
if (error46.code !== "EEXIST") throw error46;
|
|
54575
54809
|
let stale = false;
|
|
54576
54810
|
try {
|
|
54577
|
-
stale = Date.now() - (await
|
|
54811
|
+
stale = Date.now() - (await stat4(guard)).mtimeMs >= GUARD_STALE_MS;
|
|
54578
54812
|
} catch (statError) {
|
|
54579
54813
|
if (statError.code === "ENOENT") continue;
|
|
54580
54814
|
throw statError;
|
|
54581
54815
|
}
|
|
54582
54816
|
if (stale) {
|
|
54583
|
-
await
|
|
54817
|
+
await rm7(guard, { recursive: true, force: true });
|
|
54584
54818
|
continue;
|
|
54585
54819
|
}
|
|
54586
54820
|
if (Date.now() >= deadline) {
|
|
@@ -54588,7 +54822,7 @@ async function acquireGuard(directory, identity) {
|
|
|
54588
54822
|
"Timed out while checking whether this workspace is already active."
|
|
54589
54823
|
);
|
|
54590
54824
|
}
|
|
54591
|
-
await
|
|
54825
|
+
await delay5(GUARD_RETRY_MS);
|
|
54592
54826
|
}
|
|
54593
54827
|
}
|
|
54594
54828
|
}
|
|
@@ -54636,7 +54870,7 @@ async function acquireWorkspaceLease(root, options = {}) {
|
|
|
54636
54870
|
await ensureLeaseDirectory(userDirectory);
|
|
54637
54871
|
const identity = workspaceIdentity(root);
|
|
54638
54872
|
const endpoint3 = endpointFor(identity, userDirectory);
|
|
54639
|
-
const releaseGuard = await
|
|
54873
|
+
const releaseGuard = await acquireGuard2(userDirectory, identity);
|
|
54640
54874
|
let server;
|
|
54641
54875
|
try {
|
|
54642
54876
|
for (let attempt = 0; attempt < 2; attempt += 1) {
|
|
@@ -54650,11 +54884,11 @@ async function acquireWorkspaceLease(root, options = {}) {
|
|
|
54650
54884
|
if (released) return;
|
|
54651
54885
|
released = true;
|
|
54652
54886
|
await ensureLeaseDirectory(userDirectory);
|
|
54653
|
-
const releaseGuard2 = await
|
|
54887
|
+
const releaseGuard2 = await acquireGuard2(userDirectory, identity);
|
|
54654
54888
|
try {
|
|
54655
54889
|
await closeServer(server);
|
|
54656
54890
|
if (process.platform !== "win32") {
|
|
54657
|
-
await
|
|
54891
|
+
await rm7(endpoint3, { force: true });
|
|
54658
54892
|
}
|
|
54659
54893
|
} finally {
|
|
54660
54894
|
await releaseGuard2();
|
|
@@ -54667,7 +54901,7 @@ async function acquireWorkspaceLease(root, options = {}) {
|
|
|
54667
54901
|
throw new WorkspaceAlreadyActiveError();
|
|
54668
54902
|
}
|
|
54669
54903
|
if (error46.code !== "EADDRINUSE") throw error46;
|
|
54670
|
-
if (process.platform !== "win32") await
|
|
54904
|
+
if (process.platform !== "win32") await rm7(endpoint3, { force: true });
|
|
54671
54905
|
}
|
|
54672
54906
|
}
|
|
54673
54907
|
throw new Error("Could not reserve this workspace for the current Glossa process.");
|
|
@@ -54677,7 +54911,7 @@ async function acquireWorkspaceLease(root, options = {}) {
|
|
|
54677
54911
|
}
|
|
54678
54912
|
|
|
54679
54913
|
// src/main.ts
|
|
54680
|
-
var VERSION = "0.2.0
|
|
54914
|
+
var VERSION = "0.2.0";
|
|
54681
54915
|
var DISTRIBUTION = "npm";
|
|
54682
54916
|
var HELP = `Glossa ${VERSION}
|
|
54683
54917
|
|
|
@@ -54686,6 +54920,7 @@ Usage:
|
|
|
54686
54920
|
glossa status
|
|
54687
54921
|
glossa devices revoke <id>
|
|
54688
54922
|
glossa logout
|
|
54923
|
+
glossa unpair
|
|
54689
54924
|
glossa update [--check]
|
|
54690
54925
|
glossa update --policy <notify|auto|off>
|
|
54691
54926
|
glossa update --channel <beta|stable>
|
|
@@ -54705,8 +54940,8 @@ Keys:
|
|
|
54705
54940
|
\u2191\u2193 select or browse
|
|
54706
54941
|
\u2190\u2192 change workspace access
|
|
54707
54942
|
Enter/r revoke selected device
|
|
54708
|
-
Esc
|
|
54709
|
-
l sign out
|
|
54943
|
+
Esc workspace
|
|
54944
|
+
l account sign out
|
|
54710
54945
|
? help
|
|
54711
54946
|
q disconnect and quit`;
|
|
54712
54947
|
async function withLoginSignal(action) {
|
|
@@ -54753,8 +54988,8 @@ async function revokeKnownDevice(deviceId) {
|
|
|
54753
54988
|
const credentials = await authenticatedCredentials();
|
|
54754
54989
|
await revokeDevice(loadRelayEndpoints(), credentials, deviceId);
|
|
54755
54990
|
}
|
|
54756
|
-
async function runWorkspaceSession(
|
|
54757
|
-
const root = await selectExposureRoot(
|
|
54991
|
+
async function runWorkspaceSession(path14, label, accessProfile) {
|
|
54992
|
+
const root = await selectExposureRoot(path14);
|
|
54758
54993
|
const lease = await acquireWorkspaceLease(root);
|
|
54759
54994
|
let usageWrites = Promise.resolve();
|
|
54760
54995
|
const queueUsage = (write) => {
|
|
@@ -54762,8 +54997,7 @@ async function runWorkspaceSession(path13, label, accessProfile) {
|
|
|
54762
54997
|
};
|
|
54763
54998
|
try {
|
|
54764
54999
|
const endpoints = loadRelayEndpoints();
|
|
54765
|
-
|
|
54766
|
-
const statusService = new WorkspaceStatusService(credentials, endpoints);
|
|
55000
|
+
const device = await deviceForSession(endpoints);
|
|
54767
55001
|
let postExitNotice;
|
|
54768
55002
|
let requestedAccessProfile = accessProfile;
|
|
54769
55003
|
let activeSessionController;
|
|
@@ -54781,7 +55015,7 @@ async function runWorkspaceSession(path13, label, accessProfile) {
|
|
|
54781
55015
|
else signal.addEventListener("abort", stopSession, { once: true });
|
|
54782
55016
|
try {
|
|
54783
55017
|
await runManagedSession(root, endpoints, {
|
|
54784
|
-
|
|
55018
|
+
device,
|
|
54785
55019
|
workerVersion: VERSION,
|
|
54786
55020
|
accessProfile: sessionAccessProfile,
|
|
54787
55021
|
...label ? { workspaceLabel: label } : {},
|
|
@@ -54812,10 +55046,13 @@ async function runWorkspaceSession(path13, label, accessProfile) {
|
|
|
54812
55046
|
}
|
|
54813
55047
|
},
|
|
54814
55048
|
loadStatus: async (signal) => {
|
|
54815
|
-
|
|
55049
|
+
const credentials = await authenticatedCredentials(signal);
|
|
55050
|
+
return hudStatus(
|
|
55051
|
+
await new WorkspaceStatusService(credentials, endpoints).refresh(signal)
|
|
55052
|
+
);
|
|
54816
55053
|
},
|
|
54817
55054
|
revokeDevice: async (deviceId, signal) => {
|
|
54818
|
-
credentials = await
|
|
55055
|
+
const credentials = await authenticatedCredentials(signal);
|
|
54819
55056
|
await revokeDevice(
|
|
54820
55057
|
endpoints,
|
|
54821
55058
|
credentials,
|
|
@@ -54836,9 +55073,9 @@ async function runWorkspaceSession(path13, label, accessProfile) {
|
|
|
54836
55073
|
await lease.release();
|
|
54837
55074
|
}
|
|
54838
55075
|
}
|
|
54839
|
-
async function runWorkspace(
|
|
55076
|
+
async function runWorkspace(path14, label, accessProfile) {
|
|
54840
55077
|
await withWorkspaceLease(
|
|
54841
|
-
async () => await runWorkspaceSession(
|
|
55078
|
+
async () => await runWorkspaceSession(path14, label, accessProfile)
|
|
54842
55079
|
);
|
|
54843
55080
|
}
|
|
54844
55081
|
async function refreshUpdateInfo(timeoutMs) {
|
|
@@ -54941,6 +55178,8 @@ async function main() {
|
|
|
54941
55178
|
await showStatus();
|
|
54942
55179
|
} else if (invocation.command === "logout") {
|
|
54943
55180
|
await logoutFromGlossa();
|
|
55181
|
+
} else if (invocation.command === "unpair") {
|
|
55182
|
+
await unpairComputer();
|
|
54944
55183
|
} else if (invocation.command === "update") {
|
|
54945
55184
|
await runUpdateCommand(invocation);
|
|
54946
55185
|
} else {
|