@ariobarin/glossa 0.2.3-beta.1 → 0.2.4

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.
Files changed (2) hide show
  1. package/dist/app.js +1658 -529
  2. package/package.json +6 -2
package/dist/app.js CHANGED
@@ -39741,9 +39741,9 @@ function parseInvocation(args) {
39741
39741
  // src/device-format.ts
39742
39742
  function deviceStatus(device) {
39743
39743
  if (device.revokedAt) return "revoked";
39744
- if (device.activeWorkers === null) return "worker count unavailable";
39744
+ if (device.activeWorkers === null) return "workspace count unavailable";
39745
39745
  if (device.activeWorkers === 0) return "offline";
39746
- return `${device.activeWorkers} active ${device.activeWorkers === 1 ? "worker" : "workers"}`;
39746
+ return `${device.activeWorkers} active ${device.activeWorkers === 1 ? "workspace" : "workspaces"}`;
39747
39747
  }
39748
39748
  function formatRelativeTime(iso, now = Date.now()) {
39749
39749
  if (!iso) return "never";
@@ -43019,14 +43019,14 @@ function getSgrFragments(code) {
43019
43019
  return fragments;
43020
43020
  }
43021
43021
  function parseCsiCode(string4, index) {
43022
- const escapeCodePoint2 = string4.codePointAt(index);
43022
+ const escapeCodePoint3 = string4.codePointAt(index);
43023
43023
  let sequenceStartIndex;
43024
- if (escapeCodePoint2 === ESCAPE_CODE_POINT) {
43024
+ if (escapeCodePoint3 === ESCAPE_CODE_POINT) {
43025
43025
  if (string4[index + 1] !== ANSI_CSI2) {
43026
43026
  return;
43027
43027
  }
43028
43028
  sequenceStartIndex = index + 2;
43029
- } else if (escapeCodePoint2 === C1_CSI_CODE_POINT) {
43029
+ } else if (escapeCodePoint3 === C1_CSI_CODE_POINT) {
43030
43030
  sequenceStartIndex = index + 1;
43031
43031
  } else {
43032
43032
  return;
@@ -49276,61 +49276,24 @@ async function announceConnectHint(store2, log) {
49276
49276
  return true;
49277
49277
  }
49278
49278
 
49279
- // src/device-store.ts
49280
- import path3 from "node:path";
49281
- var FILE_DEVICE_WARNING = "Warning: the operating-system credential store is unavailable. Glossa is using a mode-0600 device credential file.";
49282
- function parseDeviceCredential(value) {
49283
- let parsed;
49284
- try {
49285
- parsed = JSON.parse(value);
49286
- } catch {
49287
- throw new Error("Stored Glossa device credentials are invalid.");
49288
- }
49289
- let relayOriginValid = false;
49290
- if (typeof parsed.relayOrigin === "string") {
49291
- try {
49292
- relayOriginValid = new URL(parsed.relayOrigin).origin === parsed.relayOrigin;
49293
- } catch {
49294
- relayOriginValid = false;
49295
- }
49296
- }
49297
- if (!relayOriginValid || typeof parsed.deviceId !== "string" || !/^[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(
49298
- parsed.deviceId
49299
- ) || typeof parsed.deviceName !== "string" || parsed.deviceName.length === 0 || parsed.accountSubject !== void 0 && (typeof parsed.accountSubject !== "string" || parsed.accountSubject.length === 0) || typeof parsed.token !== "string" || !parsed.token.startsWith(`gld_${parsed.deviceId}_`)) {
49300
- throw new Error("Stored Glossa device credentials are invalid.");
49301
- }
49302
- return parsed;
49303
- }
49304
- var store = new SecureStore({
49305
- account: "device",
49306
- file: path3.join(configDirectory(), "device.json"),
49307
- warning: FILE_DEVICE_WARNING,
49308
- parse: parseDeviceCredential
49309
- });
49310
- async function loadDeviceCredential() {
49311
- return (await store.load())?.value ?? null;
49312
- }
49313
- async function saveDeviceCredential(credential) {
49314
- await store.save(credential);
49315
- }
49316
- async function deleteDeviceCredential() {
49317
- await store.delete();
49318
- }
49279
+ // src/update-state.ts
49280
+ import { chmod as chmod2, mkdir as mkdir5, readFile as readFile4, rename, rm as rm4, writeFile as writeFile5 } from "node:fs/promises";
49281
+ import path5 from "node:path";
49319
49282
 
49320
- // src/device-pairing-lock.ts
49283
+ // src/process-lease.ts
49321
49284
  import { randomUUID as randomUUID2 } from "node:crypto";
49322
49285
  import { mkdir as mkdir4, readFile as readFile3, rm as rm3, stat, writeFile as writeFile4 } from "node:fs/promises";
49323
- import path5 from "node:path";
49286
+ import path4 from "node:path";
49324
49287
  import { setTimeout as delay } from "node:timers/promises";
49325
49288
 
49326
49289
  // src/update-lock.ts
49327
49290
  import { randomUUID } from "node:crypto";
49328
49291
  import { mkdir as mkdir3, readdir, rm as rm2, writeFile as writeFile3 } from "node:fs/promises";
49329
- import path4 from "node:path";
49292
+ import path3 from "node:path";
49330
49293
  var UPDATE_SUFFIX = ".update";
49331
49294
  var SESSION_SUFFIX = ".session";
49332
49295
  function updateRuntimeDirectory() {
49333
- return path4.join(configDirectory(), "runtime");
49296
+ return path3.join(configDirectory(), "runtime");
49334
49297
  }
49335
49298
  function processIsAlive(pid) {
49336
49299
  if (!Number.isSafeInteger(pid) || pid <= 0) return false;
@@ -49360,7 +49323,7 @@ async function activeLeaseFiles(directory, suffix) {
49360
49323
  for (const entry of entries) {
49361
49324
  if (!entry.isFile() || !entry.name.endsWith(suffix)) continue;
49362
49325
  const pid = leasePid(entry.name, suffix);
49363
- const file2 = path4.join(directory, entry.name);
49326
+ const file2 = path3.join(directory, entry.name);
49364
49327
  if (pid !== null && processIsAlive(pid)) active.push(file2);
49365
49328
  else await rm2(file2, { force: true });
49366
49329
  }
@@ -49374,7 +49337,7 @@ async function activeSessionFiles(directory) {
49374
49337
  }
49375
49338
  async function createUpdateLock(directory) {
49376
49339
  await mkdir3(directory, { recursive: true, mode: 448 });
49377
- const lockFile = path4.join(
49340
+ const lockFile = path3.join(
49378
49341
  directory,
49379
49342
  `${process.pid}-${randomUUID()}${UPDATE_SUFFIX}`
49380
49343
  );
@@ -49412,7 +49375,7 @@ async function withWorkspaceLease(action, directory = updateRuntimeDirectory())
49412
49375
  if (await updateLockIsActive(directory)) {
49413
49376
  throw new Error("Glossa is updating. Run this workspace again after the update finishes.");
49414
49377
  }
49415
- const leaseFile = path4.join(
49378
+ const leaseFile = path3.join(
49416
49379
  directory,
49417
49380
  `${process.pid}-${randomUUID()}${SESSION_SUFFIX}`
49418
49381
  );
@@ -49432,17 +49395,9 @@ async function withWorkspaceLease(action, directory = updateRuntimeDirectory())
49432
49395
  }
49433
49396
  }
49434
49397
 
49435
- // src/device-pairing-lock.ts
49436
- var DEVICE_PAIRING_LOCK = "device-pairing.lock";
49437
- var DEVICE_PAIRING_LOCK_GUARD = "device-pairing.lock.guard";
49438
- var DEVICE_PAIRING_LOCK_POLL_MS = 100;
49439
- var DEVICE_PAIRING_LOCK_MAX_AGE_MS = 6 * 6e4;
49440
- var DEVICE_PAIRING_GUARD_MAX_AGE_MS = 3e4;
49441
- function lockPath(directory) {
49442
- return path5.join(directory, DEVICE_PAIRING_LOCK);
49443
- }
49444
- function guardPath(directory) {
49445
- return path5.join(directory, DEVICE_PAIRING_LOCK_GUARD);
49398
+ // src/process-lease.ts
49399
+ function guardName(lockName) {
49400
+ return `${lockName}.guard`;
49446
49401
  }
49447
49402
  function newOwner() {
49448
49403
  return {
@@ -49477,8 +49432,8 @@ async function lockIsStale(file2, maxAgeMs) {
49477
49432
  const ageMs = Date.now() - Date.parse(owner.startedAt);
49478
49433
  return ageMs > maxAgeMs || !processIsAlive(owner.pid);
49479
49434
  }
49480
- async function acquireGuard(directory, signal) {
49481
- const file2 = guardPath(directory);
49435
+ async function acquireGuard(directory, options, signal) {
49436
+ const file2 = path4.join(directory, guardName(options.lockName));
49482
49437
  for (; ; ) {
49483
49438
  signal?.throwIfAborted();
49484
49439
  const owner = newOwner();
@@ -49493,32 +49448,28 @@ async function acquireGuard(directory, signal) {
49493
49448
  } catch (error46) {
49494
49449
  if (error46.code !== "EEXIST") throw error46;
49495
49450
  }
49496
- if (await lockIsStale(file2, DEVICE_PAIRING_GUARD_MAX_AGE_MS)) {
49451
+ if (await lockIsStale(file2, options.guardMaxAgeMs)) {
49497
49452
  await rm3(file2, { force: true });
49498
49453
  continue;
49499
49454
  }
49500
- await delay(
49501
- DEVICE_PAIRING_LOCK_POLL_MS,
49502
- void 0,
49503
- signal ? { signal } : void 0
49504
- );
49455
+ await delay(options.pollMs, void 0, signal ? { signal } : void 0);
49505
49456
  }
49506
49457
  }
49507
49458
  async function removeOwnedLock(file2, token) {
49508
49459
  const owner = await readOwner(file2);
49509
49460
  if (owner?.token === token) await rm3(file2, { force: true });
49510
49461
  }
49511
- async function withGuard(directory, action, signal) {
49512
- const guard = await acquireGuard(directory, signal);
49462
+ async function withGuard(directory, options, action, signal) {
49463
+ const guard = await acquireGuard(directory, options, signal);
49513
49464
  try {
49514
49465
  return await action();
49515
49466
  } finally {
49516
49467
  await removeOwnedLock(guard.file, guard.token);
49517
49468
  }
49518
49469
  }
49519
- async function acquireDevicePairingLock(directory, signal) {
49470
+ async function acquireProcessLease(directory, options, signal) {
49520
49471
  await mkdir4(directory, { recursive: true, mode: 448 });
49521
- const file2 = lockPath(directory);
49472
+ const file2 = path4.join(directory, options.lockName);
49522
49473
  for (; ; ) {
49523
49474
  signal?.throwIfAborted();
49524
49475
  const owner = newOwner();
@@ -49535,30 +49486,189 @@ async function acquireDevicePairingLock(directory, signal) {
49535
49486
  }
49536
49487
  const reclaimed = await withGuard(
49537
49488
  directory,
49489
+ options,
49538
49490
  async () => {
49539
- if (!await lockIsStale(file2, DEVICE_PAIRING_LOCK_MAX_AGE_MS)) {
49540
- return false;
49541
- }
49491
+ if (!await lockIsStale(file2, options.maxAgeMs)) return false;
49542
49492
  await rm3(file2, { force: true });
49543
49493
  return true;
49544
49494
  },
49545
49495
  signal
49546
49496
  );
49547
49497
  if (reclaimed) continue;
49548
- await delay(
49549
- DEVICE_PAIRING_LOCK_POLL_MS,
49550
- void 0,
49551
- signal ? { signal } : void 0
49552
- );
49498
+ await delay(options.pollMs, void 0, signal ? { signal } : void 0);
49553
49499
  }
49554
49500
  }
49555
- async function withDevicePairingLease(action, signal, directory = updateRuntimeDirectory()) {
49556
- const lock = await acquireDevicePairingLock(directory, signal);
49501
+ async function withProcessLease(action, options, signal, directory) {
49502
+ const lock = await acquireProcessLease(directory, options, signal);
49557
49503
  try {
49558
49504
  return await action();
49559
49505
  } finally {
49560
- await withGuard(directory, async () => await removeOwnedLock(lock.file, lock.token));
49506
+ await withGuard(
49507
+ directory,
49508
+ options,
49509
+ async () => await removeOwnedLock(lock.file, lock.token),
49510
+ signal
49511
+ );
49512
+ }
49513
+ }
49514
+
49515
+ // src/update-state.ts
49516
+ var UPDATE_CHECK_INTERVAL_MS = 24 * 60 * 60 * 1e3;
49517
+ var UPDATE_STATE_LOCK_POLL_MS = 25;
49518
+ var UPDATE_STATE_LOCK_MAX_AGE_MS = 6e4;
49519
+ var UPDATE_STATE_GUARD_MAX_AGE_MS = 3e4;
49520
+ function defaultUpdateChannel(version2) {
49521
+ return version2.includes("-") ? "beta" : "stable";
49522
+ }
49523
+ function updateStateFile() {
49524
+ return path5.join(configDirectory(), "updates.json");
49525
+ }
49526
+ function isPolicy(value) {
49527
+ return value === "notify" || value === "auto" || value === "off";
49528
+ }
49529
+ function isChannel(value) {
49530
+ return value === "beta" || value === "stable";
49531
+ }
49532
+ function optionalString(value) {
49533
+ return typeof value === "string" && value.length > 0 ? value : void 0;
49534
+ }
49535
+ async function withUpdateStateLease(action, file2 = updateStateFile()) {
49536
+ return await withProcessLease(
49537
+ action,
49538
+ {
49539
+ lockName: `${path5.basename(file2)}.lock`,
49540
+ pollMs: UPDATE_STATE_LOCK_POLL_MS,
49541
+ maxAgeMs: UPDATE_STATE_LOCK_MAX_AGE_MS,
49542
+ guardMaxAgeMs: UPDATE_STATE_GUARD_MAX_AGE_MS
49543
+ },
49544
+ void 0,
49545
+ path5.dirname(file2)
49546
+ );
49547
+ }
49548
+ async function loadUpdateState(currentVersion, file2 = updateStateFile()) {
49549
+ const defaults = {
49550
+ policy: "notify",
49551
+ channel: defaultUpdateChannel(currentVersion)
49552
+ };
49553
+ try {
49554
+ const parsed = JSON.parse(await readFile4(file2, "utf8"));
49555
+ const lastCheckedAt = optionalString(parsed.lastCheckedAt);
49556
+ const mcpContractVersion = optionalString(parsed.mcpContractVersion);
49557
+ return {
49558
+ policy: isPolicy(parsed.policy) ? parsed.policy : defaults.policy,
49559
+ channel: isChannel(parsed.channel) ? parsed.channel : defaults.channel,
49560
+ ...lastCheckedAt ? { lastCheckedAt } : {},
49561
+ ...mcpContractVersion ? { mcpContractVersion } : {}
49562
+ };
49563
+ } catch (error46) {
49564
+ if (error46.code === "ENOENT" || error46 instanceof SyntaxError) {
49565
+ return defaults;
49566
+ }
49567
+ throw error46;
49568
+ }
49569
+ }
49570
+ async function saveUpdateState(state, file2) {
49571
+ const directory = path5.dirname(file2);
49572
+ const temporary = `${file2}.${process.pid}.tmp`;
49573
+ await mkdir5(directory, { recursive: true, mode: 448 });
49574
+ try {
49575
+ await writeFile5(temporary, `${JSON.stringify(state, null, 2)}
49576
+ `, {
49577
+ encoding: "utf8",
49578
+ mode: 384
49579
+ });
49580
+ if (process.platform !== "win32") await chmod2(temporary, 384);
49581
+ await rename(temporary, file2);
49582
+ } finally {
49583
+ await rm4(temporary, { force: true });
49584
+ }
49585
+ }
49586
+ async function configureUpdates(currentVersion, changes, file2 = updateStateFile()) {
49587
+ return await withUpdateStateLease(async () => {
49588
+ const previous = await loadUpdateState(currentVersion, file2);
49589
+ const state = {
49590
+ policy: changes.policy ?? previous.policy,
49591
+ channel: changes.channel ?? previous.channel,
49592
+ ...previous.mcpContractVersion ? { mcpContractVersion: previous.mcpContractVersion } : {}
49593
+ };
49594
+ await saveUpdateState(state, file2);
49595
+ return state;
49596
+ }, file2);
49597
+ }
49598
+ async function recordUpdateCheck(currentVersion, checkedAt = /* @__PURE__ */ new Date(), file2 = updateStateFile()) {
49599
+ return await withUpdateStateLease(async () => {
49600
+ const state = {
49601
+ ...await loadUpdateState(currentVersion, file2),
49602
+ lastCheckedAt: checkedAt.toISOString()
49603
+ };
49604
+ await saveUpdateState(state, file2);
49605
+ return state;
49606
+ }, file2);
49607
+ }
49608
+ async function observeMcpContractVersion(currentVersion, mcpContractVersion, file2 = updateStateFile()) {
49609
+ return await withUpdateStateLease(async () => {
49610
+ const previous = await loadUpdateState(currentVersion, file2);
49611
+ if (previous.mcpContractVersion === mcpContractVersion) return false;
49612
+ await saveUpdateState({ ...previous, mcpContractVersion }, file2);
49613
+ return previous.mcpContractVersion !== void 0;
49614
+ }, file2);
49615
+ }
49616
+ function isUpdateCheckDue(lastCheckedAt, now = Date.now()) {
49617
+ if (!lastCheckedAt) return true;
49618
+ const checkedAt = Date.parse(lastCheckedAt);
49619
+ return !Number.isFinite(checkedAt) || now - checkedAt >= UPDATE_CHECK_INTERVAL_MS;
49620
+ }
49621
+
49622
+ // src/device-store.ts
49623
+ import path6 from "node:path";
49624
+ var FILE_DEVICE_WARNING = "Warning: the operating-system credential store is unavailable. Glossa is using a mode-0600 device credential file.";
49625
+ function parseDeviceCredential(value) {
49626
+ let parsed;
49627
+ try {
49628
+ parsed = JSON.parse(value);
49629
+ } catch {
49630
+ throw new Error("Stored Glossa device credentials are invalid.");
49631
+ }
49632
+ let relayOriginValid = false;
49633
+ if (typeof parsed.relayOrigin === "string") {
49634
+ try {
49635
+ relayOriginValid = new URL(parsed.relayOrigin).origin === parsed.relayOrigin;
49636
+ } catch {
49637
+ relayOriginValid = false;
49638
+ }
49639
+ }
49640
+ if (!relayOriginValid || typeof parsed.deviceId !== "string" || !/^[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(
49641
+ parsed.deviceId
49642
+ ) || typeof parsed.deviceName !== "string" || parsed.deviceName.length === 0 || parsed.accountSubject !== void 0 && (typeof parsed.accountSubject !== "string" || parsed.accountSubject.length === 0) || typeof parsed.token !== "string" || !parsed.token.startsWith(`gld_${parsed.deviceId}_`)) {
49643
+ throw new Error("Stored Glossa device credentials are invalid.");
49561
49644
  }
49645
+ return parsed;
49646
+ }
49647
+ var store = new SecureStore({
49648
+ account: "device",
49649
+ file: path6.join(configDirectory(), "device.json"),
49650
+ warning: FILE_DEVICE_WARNING,
49651
+ parse: parseDeviceCredential
49652
+ });
49653
+ async function loadDeviceCredential() {
49654
+ return (await store.load())?.value ?? null;
49655
+ }
49656
+ async function saveDeviceCredential(credential) {
49657
+ await store.save(credential);
49658
+ }
49659
+ async function deleteDeviceCredential() {
49660
+ await store.delete();
49661
+ }
49662
+
49663
+ // src/device-pairing-lock.ts
49664
+ var DEVICE_PAIRING_LEASE = {
49665
+ lockName: "device-pairing.lock",
49666
+ pollMs: 100,
49667
+ maxAgeMs: 6 * 6e4,
49668
+ guardMaxAgeMs: 3e4
49669
+ };
49670
+ async function withDevicePairingLease(action, signal, directory = updateRuntimeDirectory()) {
49671
+ return await withProcessLease(action, DEVICE_PAIRING_LEASE, signal, directory);
49562
49672
  }
49563
49673
 
49564
49674
  // src/device-pairing.ts
@@ -50208,27 +50318,27 @@ var CommandService = class {
50208
50318
  // src/worker/file-service.ts
50209
50319
  import { createHash, randomUUID as randomUUID4 } from "node:crypto";
50210
50320
  import {
50211
- chmod as chmod2,
50321
+ chmod as chmod3,
50212
50322
  link as link2,
50213
50323
  lstat as lstat2,
50214
- mkdir as mkdir5,
50324
+ mkdir as mkdir6,
50215
50325
  opendir,
50216
50326
  open,
50217
- readFile as readFile4,
50218
- rename,
50219
- rm as rm4,
50327
+ readFile as readFile5,
50328
+ rename as rename2,
50329
+ rm as rm5,
50220
50330
  rmdir,
50221
50331
  stat as stat3,
50222
- writeFile as writeFile5
50332
+ writeFile as writeFile6
50223
50333
  } from "node:fs/promises";
50224
- import path7 from "node:path";
50334
+ import path8 from "node:path";
50225
50335
  import { performance as performance2 } from "node:perf_hooks";
50226
50336
  import { StringDecoder as StringDecoder2 } from "node:string_decoder";
50227
50337
 
50228
50338
  // src/worker/path-policy.ts
50229
50339
  import { lstat, realpath, stat as stat2 } from "node:fs/promises";
50230
50340
  import os5 from "node:os";
50231
- import path6 from "node:path";
50341
+ import path7 from "node:path";
50232
50342
  function samePath(left, right) {
50233
50343
  return process.platform === "win32" ? left.toLowerCase() === right.toLowerCase() : left === right;
50234
50344
  }
@@ -50240,15 +50350,15 @@ function accountHomeDirectory() {
50240
50350
  }
50241
50351
  }
50242
50352
  function isWithin(root, candidate) {
50243
- const relative = path6.relative(root, candidate);
50244
- return relative === "" || !relative.startsWith(`..${path6.sep}`) && relative !== ".." && !path6.isAbsolute(relative);
50353
+ const relative = path7.relative(root, candidate);
50354
+ return relative === "" || !relative.startsWith(`..${path7.sep}`) && relative !== ".." && !path7.isAbsolute(relative);
50245
50355
  }
50246
50356
  function validateRelativePath(value) {
50247
50357
  if (value.includes("\0")) {
50248
50358
  throw new WorkerError("invalid_path", "Paths cannot contain null bytes.");
50249
50359
  }
50250
50360
  const explicitNativePosixPath = process.platform !== "win32" && value.startsWith("./");
50251
- if (path6.isAbsolute(value) || path6.posix.isAbsolute(value) || !explicitNativePosixPath && path6.win32.isAbsolute(value)) {
50361
+ if (path7.isAbsolute(value) || path7.posix.isAbsolute(value) || !explicitNativePosixPath && path7.win32.isAbsolute(value)) {
50252
50362
  throw new WorkerError("absolute_path", "Absolute paths are not allowed.");
50253
50363
  }
50254
50364
  const segments = explicitNativePosixPath ? value.split(/\/+/).filter(Boolean) : value.split(/[\\/]+/);
@@ -50258,7 +50368,7 @@ function validateRelativePath(value) {
50258
50368
  return value === "" ? "." : value;
50259
50369
  }
50260
50370
  async function canonicalizeRoot(candidate) {
50261
- const root = await realpath(path6.resolve(candidate)).catch((error46) => {
50371
+ const root = await realpath(path7.resolve(candidate)).catch((error46) => {
50262
50372
  if (error46.code === "ENOENT") {
50263
50373
  throw new WorkerError("root_not_found", "The workspace directory does not exist.");
50264
50374
  }
@@ -50268,10 +50378,10 @@ async function canonicalizeRoot(candidate) {
50268
50378
  if (!rootStat.isDirectory()) {
50269
50379
  throw new WorkerError("root_not_directory", "The exposed root must be a directory.");
50270
50380
  }
50271
- const filesystemRoot = path6.parse(root).root;
50381
+ const filesystemRoot = path7.parse(root).root;
50272
50382
  const homes = await Promise.all(
50273
50383
  [os5.homedir(), accountHomeDirectory()].map(
50274
- async (home) => await realpath(home).catch(() => path6.resolve(home))
50384
+ async (home) => await realpath(home).catch(() => path7.resolve(home))
50275
50385
  )
50276
50386
  );
50277
50387
  const isHomeDirectory = homes.some((home) => samePath(root, home));
@@ -50307,7 +50417,7 @@ var PathPolicy = class _PathPolicy {
50307
50417
  return canonical;
50308
50418
  }
50309
50419
  async resolveDiscoveredExisting(candidate) {
50310
- const lexical = path6.resolve(candidate);
50420
+ const lexical = path7.resolve(candidate);
50311
50421
  if (!isWithin(this.root, lexical)) {
50312
50422
  throw new WorkerError("path_escape", "The requested path escapes the exposed root.");
50313
50423
  }
@@ -50325,7 +50435,7 @@ var PathPolicy = class _PathPolicy {
50325
50435
  }
50326
50436
  async resolveWritableFile(relativePath) {
50327
50437
  const lexical = this.resolveLexical(relativePath);
50328
- const parent = path6.dirname(lexical);
50438
+ const parent = path7.dirname(lexical);
50329
50439
  await this.rejectLinkedComponents(parent);
50330
50440
  const canonicalParent = await realpath(parent).catch((error46) => {
50331
50441
  if (error46.code === "ENOENT") {
@@ -50356,7 +50466,7 @@ var PathPolicy = class _PathPolicy {
50356
50466
  if (error46 instanceof WorkerError) throw error46;
50357
50467
  if (error46.code !== "ENOENT") throw error46;
50358
50468
  }
50359
- return path6.join(canonicalParent, path6.basename(lexical));
50469
+ return path7.join(canonicalParent, path7.basename(lexical));
50360
50470
  }
50361
50471
  async resolveWritableDirectory(relativePath, recursive) {
50362
50472
  const lexical = this.resolveLexical(relativePath);
@@ -50379,7 +50489,7 @@ var PathPolicy = class _PathPolicy {
50379
50489
  if (error46.code !== "ENOENT") throw error46;
50380
50490
  }
50381
50491
  if (!recursive) {
50382
- const parent = path6.dirname(lexical);
50492
+ const parent = path7.dirname(lexical);
50383
50493
  await this.rejectLinkedComponents(parent);
50384
50494
  const canonicalParent = await realpath(parent).catch(
50385
50495
  (error46) => {
@@ -50399,11 +50509,11 @@ var PathPolicy = class _PathPolicy {
50399
50509
  throw new WorkerError("not_directory", "The destination parent is not a directory.");
50400
50510
  }
50401
50511
  return {
50402
- target: path6.join(canonicalParent, path6.basename(lexical)),
50512
+ target: path7.join(canonicalParent, path7.basename(lexical)),
50403
50513
  exists: false
50404
50514
  };
50405
50515
  }
50406
- let existingAncestor = path6.dirname(lexical);
50516
+ let existingAncestor = path7.dirname(lexical);
50407
50517
  while (!samePath(existingAncestor, this.root)) {
50408
50518
  try {
50409
50519
  await this.rejectLinkedComponents(existingAncestor);
@@ -50415,9 +50525,9 @@ var PathPolicy = class _PathPolicy {
50415
50525
  throw new WorkerError("not_directory", "The destination parent is not a directory.");
50416
50526
  }
50417
50527
  return {
50418
- target: path6.join(
50528
+ target: path7.join(
50419
50529
  canonicalAncestor,
50420
- path6.relative(existingAncestor, lexical)
50530
+ path7.relative(existingAncestor, lexical)
50421
50531
  ),
50422
50532
  exists: false
50423
50533
  };
@@ -50425,13 +50535,13 @@ var PathPolicy = class _PathPolicy {
50425
50535
  if (error46 instanceof WorkerError) throw error46;
50426
50536
  if (error46.code !== "ENOENT") throw error46;
50427
50537
  }
50428
- existingAncestor = path6.dirname(existingAncestor);
50538
+ existingAncestor = path7.dirname(existingAncestor);
50429
50539
  }
50430
50540
  return { target: lexical, exists: false };
50431
50541
  }
50432
50542
  async resolveVacantPath(relativePath) {
50433
50543
  const lexical = this.resolveLexical(relativePath);
50434
- const parent = path6.dirname(lexical);
50544
+ const parent = path7.dirname(lexical);
50435
50545
  await this.rejectLinkedComponents(parent);
50436
50546
  const canonicalParent = await realpath(parent).catch(
50437
50547
  (error46) => {
@@ -50460,11 +50570,11 @@ var PathPolicy = class _PathPolicy {
50460
50570
  if (error46 instanceof WorkerError) throw error46;
50461
50571
  if (error46.code !== "ENOENT") throw error46;
50462
50572
  }
50463
- return path6.join(canonicalParent, path6.basename(lexical));
50573
+ return path7.join(canonicalParent, path7.basename(lexical));
50464
50574
  }
50465
50575
  resolveLexical(relativePath) {
50466
50576
  const validated = validateRelativePath(relativePath);
50467
- const candidate = path6.resolve(this.root, validated);
50577
+ const candidate = path7.resolve(this.root, validated);
50468
50578
  if (!isWithin(this.root, candidate)) {
50469
50579
  throw new WorkerError("path_escape", "The requested path escapes the exposed root.");
50470
50580
  }
@@ -50474,11 +50584,11 @@ var PathPolicy = class _PathPolicy {
50474
50584
  if (!isWithin(this.root, candidate)) {
50475
50585
  throw new WorkerError("path_escape", "The requested path escapes the exposed root.");
50476
50586
  }
50477
- const relative = path6.relative(this.root, candidate);
50587
+ const relative = path7.relative(this.root, candidate);
50478
50588
  if (!relative) return;
50479
50589
  let current = this.root;
50480
- for (const segment of relative.split(path6.sep)) {
50481
- current = path6.join(current, segment);
50590
+ for (const segment of relative.split(path7.sep)) {
50591
+ current = path7.join(current, segment);
50482
50592
  try {
50483
50593
  const currentStat = await lstat(current);
50484
50594
  if (currentStat.isSymbolicLink()) {
@@ -50513,12 +50623,12 @@ function imageMimeType(content) {
50513
50623
  return void 0;
50514
50624
  }
50515
50625
  function isPathWithin(root, candidate) {
50516
- const relative = path7.relative(root, candidate);
50517
- return relative === "" || !relative.startsWith(`..${path7.sep}`) && relative !== ".." && !path7.isAbsolute(relative);
50626
+ const relative = path8.relative(root, candidate);
50627
+ return relative === "" || !relative.startsWith(`..${path8.sep}`) && relative !== ".." && !path8.isAbsolute(relative);
50518
50628
  }
50519
50629
  var fileWriteTails = /* @__PURE__ */ new Map();
50520
50630
  async function withFileWriteLock(target, operation) {
50521
- const normalized = path7.normalize(target);
50631
+ const normalized = path8.normalize(target);
50522
50632
  const key = process.platform === "win32" ? normalized.toLowerCase() : normalized;
50523
50633
  const predecessor = fileWriteTails.get(key);
50524
50634
  let release;
@@ -50535,7 +50645,7 @@ async function withFileWriteLock(target, operation) {
50535
50645
  }
50536
50646
  }
50537
50647
  async function withFileWriteLocks(targets, operation) {
50538
- const unique = [...new Set(targets.map((target) => path7.normalize(target)))].sort(
50648
+ const unique = [...new Set(targets.map((target) => path8.normalize(target)))].sort(
50539
50649
  (left, right) => left.localeCompare(right)
50540
50650
  );
50541
50651
  const run = async (index) => {
@@ -50547,7 +50657,7 @@ async function withFileWriteLocks(targets, operation) {
50547
50657
  async function requireRevision(target, expectedSha256) {
50548
50658
  let actual = null;
50549
50659
  try {
50550
- actual = sha256(await readFile4(target));
50660
+ actual = sha256(await readFile5(target));
50551
50661
  } catch (error46) {
50552
50662
  if (error46.code !== "ENOENT") throw error46;
50553
50663
  }
@@ -50571,7 +50681,7 @@ function compareNames(left, right) {
50571
50681
  return left < right ? -1 : left > right ? 1 : 0;
50572
50682
  }
50573
50683
  function relativeSortKey(root, target) {
50574
- const relative = path7.relative(root, target);
50684
+ const relative = path8.relative(root, target);
50575
50685
  if (!relative) return ".";
50576
50686
  return process.platform === "win32" ? relative.replaceAll("\\", "/") : relative;
50577
50687
  }
@@ -50992,7 +51102,7 @@ var FileService = class {
50992
51102
  if (batch.overflow) throw scanLimitError();
50993
51103
  scannedEntries += batch.children.length;
50994
51104
  for (const child of batch.children) {
50995
- const target = path7.join(directory, child.name);
51105
+ const target = path8.join(directory, child.name);
50996
51106
  heapPush(frontier, {
50997
51107
  target,
50998
51108
  sortKey: relativeSortKey(this.policy.root, target),
@@ -51016,7 +51126,7 @@ var FileService = class {
51016
51126
  throw scanLimitError();
51017
51127
  }
51018
51128
  scannedEntries += 1;
51019
- const target = path7.join(directory, child.name);
51129
+ const target = path8.join(directory, child.name);
51020
51130
  let childStat;
51021
51131
  try {
51022
51132
  childStat = await this.#withinDeadline(
@@ -51168,7 +51278,7 @@ var FileService = class {
51168
51278
  if (!patterns) return false;
51169
51279
  const normalized = relativePath.replaceAll("\\", "/");
51170
51280
  try {
51171
- return patterns.some((pattern) => path7.posix.matchesGlob(normalized, pattern));
51281
+ return patterns.some((pattern) => path8.posix.matchesGlob(normalized, pattern));
51172
51282
  } catch {
51173
51283
  throw new WorkerError("invalid_search", "Search glob pattern is invalid.");
51174
51284
  }
@@ -51197,7 +51307,7 @@ var FileService = class {
51197
51307
  const searchFile = async (target) => {
51198
51308
  const relative = displayPath(this.policy.root, target);
51199
51309
  if (extensions && !extensions.some(
51200
- (extension) => path7.basename(target).toLowerCase().endsWith(extension)
51310
+ (extension) => path8.basename(target).toLowerCase().endsWith(extension)
51201
51311
  )) {
51202
51312
  return false;
51203
51313
  }
@@ -51292,7 +51402,7 @@ var FileService = class {
51292
51402
  scannedEntries += batch.children.length;
51293
51403
  for (const child of batch.children) {
51294
51404
  this.#assertBeforeDeadline(deadlineAt);
51295
- const target = path7.join(directory, child.name);
51405
+ const target = path8.join(directory, child.name);
51296
51406
  let targetType;
51297
51407
  if (this.#trustDirectoryEntryTypes) {
51298
51408
  if (child.isSymbolicLink()) {
@@ -51440,7 +51550,7 @@ var FileService = class {
51440
51550
  );
51441
51551
  if (current.exists) return { created: false };
51442
51552
  try {
51443
- await mkdir5(current.target, { recursive });
51553
+ await mkdir6(current.target, { recursive });
51444
51554
  } catch (error46) {
51445
51555
  if (error46.code !== "EEXIST") throw error46;
51446
51556
  }
@@ -51477,12 +51587,12 @@ var FileService = class {
51477
51587
  try {
51478
51588
  if (targetStat.isDirectory()) {
51479
51589
  if (recursive) {
51480
- await rm4(target, { recursive: true, force: false });
51590
+ await rm5(target, { recursive: true, force: false });
51481
51591
  } else {
51482
51592
  await rmdir(target);
51483
51593
  }
51484
51594
  } else {
51485
- await rm4(target, { force: false });
51595
+ await rm5(target, { force: false });
51486
51596
  }
51487
51597
  } catch (error46) {
51488
51598
  const code = error46.code;
@@ -51532,7 +51642,7 @@ var FileService = class {
51532
51642
  "A directory cannot be moved inside itself."
51533
51643
  );
51534
51644
  }
51535
- await rename(source, destination);
51645
+ await rename2(source, destination);
51536
51646
  return {
51537
51647
  movedType: sourceStat.isDirectory() ? "directory" : "file"
51538
51648
  };
@@ -51602,20 +51712,20 @@ var FileService = class {
51602
51712
  );
51603
51713
  }
51604
51714
  if (expectedSha256) await requireRevision(target, expectedSha256);
51605
- const temporary = path7.join(path7.dirname(target), `.glossa-${randomUUID4()}.tmp`);
51715
+ const temporary = path8.join(path8.dirname(target), `.glossa-${randomUUID4()}.tmp`);
51606
51716
  try {
51607
- await writeFile5(temporary, bytes, { flag: "wx", mode: 384 });
51717
+ await writeFile6(temporary, bytes, { flag: "wx", mode: 384 });
51608
51718
  target = await this.policy.resolveWritableFile(relativePath);
51609
51719
  const tempStat = await lstat2(temporary);
51610
51720
  if (!tempStat.isFile() || tempStat.isSymbolicLink()) {
51611
51721
  throw new WorkerError("unsafe_temporary_file", "The atomic write temporary file changed.");
51612
51722
  }
51613
51723
  if (existingMode !== void 0 && process.platform !== "win32") {
51614
- await chmod2(temporary, existingMode);
51724
+ await chmod3(temporary, existingMode);
51615
51725
  }
51616
51726
  if (expectedSha256) {
51617
51727
  await requireRevision(target, expectedSha256);
51618
- await rename(temporary, target);
51728
+ await rename2(temporary, target);
51619
51729
  } else {
51620
51730
  try {
51621
51731
  await link2(temporary, target);
@@ -51630,7 +51740,7 @@ var FileService = class {
51630
51740
  }
51631
51741
  }
51632
51742
  } finally {
51633
- await rm4(temporary, { force: true });
51743
+ await rm5(temporary, { force: true });
51634
51744
  }
51635
51745
  return { sha256: sha256(bytes), bytes: bytes.byteLength };
51636
51746
  });
@@ -52016,7 +52126,8 @@ var RemoteWorker = class {
52016
52126
  registeredSession = session;
52017
52127
  this.#onStatus({
52018
52128
  state: "connected",
52019
- reconnected: connectedBefore
52129
+ reconnected: connectedBefore,
52130
+ ...session.mcpContractVersion ? { mcpContractVersion: session.mcpContractVersion } : {}
52020
52131
  });
52021
52132
  connectedBefore = true;
52022
52133
  failures = 0;
@@ -52087,10 +52198,12 @@ var RemoteWorker = class {
52087
52198
  if (typeof value !== "object" || value === null || !("generation" in value) || typeof value.generation !== "string" || !("workerId" in value) || value.workerId !== this.#workerId || !("workerToken" in value) || !("accessProfile" in value) || value.accessProfile !== this.#accessProfile || !acceptsCapabilities(value, imageReads) || this.#workspaceLabel !== void 0 && (!("workspaceLabel" in value) || value.workspaceLabel !== this.#workspaceLabel)) {
52088
52199
  throw new RelayProtocolUnsupportedError();
52089
52200
  }
52201
+ const mcpContractVersion = "mcpContractVersion" in value && typeof value.mcpContractVersion === "string" && value.mcpContractVersion.length > 0 && value.mcpContractVersion.length <= 32 ? value.mcpContractVersion : void 0;
52090
52202
  return {
52091
52203
  generation: value.generation,
52092
52204
  workerToken: requiredWorkerToken(value.workerToken),
52093
- imageReads
52205
+ imageReads,
52206
+ ...mcpContractVersion ? { mcpContractVersion } : {}
52094
52207
  };
52095
52208
  }
52096
52209
  async #pollGeneration(session) {
@@ -52315,6 +52428,196 @@ function activityResultLabel(job, result) {
52315
52428
  }
52316
52429
  return `${job.type} completed`;
52317
52430
  }
52431
+ var MAX_ACTIVITY_OUTPUT_CHARS = 512;
52432
+ var OUTPUT_TRUNCATION_MARKER = "\u2026 output truncated \u2026";
52433
+ function isRecord(value) {
52434
+ return typeof value === "object" && value !== null;
52435
+ }
52436
+ function escapeActivityOutputControls(text) {
52437
+ let escaped = "";
52438
+ for (const character of text) {
52439
+ const code = character.codePointAt(0);
52440
+ if (character === "\n") {
52441
+ escaped += character;
52442
+ } else if (code < 32 || code === 127 || code >= 8234 && code <= 8238 || code >= 8294 && code <= 8297) {
52443
+ escaped += `\\u${code.toString(16).padStart(4, "0")}`;
52444
+ } else {
52445
+ escaped += character;
52446
+ }
52447
+ }
52448
+ return escaped;
52449
+ }
52450
+ function boundedActivityPreview(text, sourceTruncated = false) {
52451
+ const normalized = text.replaceAll("\r\n", "\n").trimEnd();
52452
+ if (containsRestrictedAuthenticationData(normalized)) {
52453
+ return { preview: "[restricted output blocked]" };
52454
+ }
52455
+ const safe = escapeActivityOutputControls(normalized);
52456
+ if (!safe) return sourceTruncated ? { preview: OUTPUT_TRUNCATION_MARKER, truncated: true } : {};
52457
+ const characters = Array.from(safe);
52458
+ if (characters.length <= MAX_ACTIVITY_OUTPUT_CHARS && (!sourceTruncated || characters.length + OUTPUT_TRUNCATION_MARKER.length + 1 <= MAX_ACTIVITY_OUTPUT_CHARS)) {
52459
+ return sourceTruncated ? { preview: `${safe}
52460
+ ${OUTPUT_TRUNCATION_MARKER}`, truncated: true } : { preview: safe };
52461
+ }
52462
+ const marker = `
52463
+ ${OUTPUT_TRUNCATION_MARKER}
52464
+ `;
52465
+ const remaining = Math.max(2, MAX_ACTIVITY_OUTPUT_CHARS - marker.length);
52466
+ const headLength = Math.floor(remaining * 0.7);
52467
+ const tailLength = remaining - headLength;
52468
+ return {
52469
+ preview: `${characters.slice(0, headLength).join("")}${marker}${characters.slice(-tailLength).join("")}`,
52470
+ truncated: true
52471
+ };
52472
+ }
52473
+ function formatOutputBytes(bytes) {
52474
+ if (typeof bytes !== "number" || !Number.isFinite(bytes) || bytes < 0) return void 0;
52475
+ if (bytes < 1024) return `${bytes} B`;
52476
+ const kibibytes = bytes / 1024;
52477
+ if (kibibytes < 1024) return `${kibibytes.toFixed(kibibytes < 10 ? 1 : 0)} KiB`;
52478
+ return `${(kibibytes / 1024).toFixed(1)} MiB`;
52479
+ }
52480
+ function activitySuccess(preview, sourceTruncated = false) {
52481
+ return {
52482
+ kind: "success",
52483
+ ...boundedActivityPreview(preview, sourceTruncated)
52484
+ };
52485
+ }
52486
+ function commandActivityOutput(value) {
52487
+ if (!isRecord(value) || typeof value.status !== "string") return void 0;
52488
+ const status = value.status;
52489
+ let kind;
52490
+ let fallback;
52491
+ if (status === "running") {
52492
+ kind = "running";
52493
+ fallback = "Command started and is still running.";
52494
+ } else if (status === "succeeded") {
52495
+ kind = "success";
52496
+ fallback = "Command completed successfully.";
52497
+ } else if (status === "canceled") {
52498
+ kind = "success";
52499
+ fallback = "Command canceled.";
52500
+ } else if (status === "timed_out") {
52501
+ kind = "error";
52502
+ fallback = "Command timed out.";
52503
+ } else if (status === "failed") {
52504
+ kind = "error";
52505
+ fallback = typeof value.exitCode === "number" ? `Command failed with exit code ${value.exitCode}.` : "Command failed.";
52506
+ } else {
52507
+ return void 0;
52508
+ }
52509
+ const stdout = typeof value.stdout === "string" ? value.stdout : "";
52510
+ const stderr = typeof value.stderr === "string" ? value.stderr : "";
52511
+ const preferError = kind === "error";
52512
+ const previewText = preferError ? stderr || stdout : stdout || stderr;
52513
+ const sourceTruncated = previewText === stderr ? value.stderrTruncated === true : value.stdoutTruncated === true;
52514
+ return {
52515
+ kind,
52516
+ ...boundedActivityPreview(previewText || fallback, sourceTruncated)
52517
+ };
52518
+ }
52519
+ function listFilesActivityOutput(value) {
52520
+ if (!isRecord(value) || !Array.isArray(value.entries)) return void 0;
52521
+ const lines = value.entries.flatMap((entry) => {
52522
+ if (!isRecord(entry) || typeof entry.path !== "string") return [];
52523
+ const suffix = entry.type === "directory" ? "/" : formatOutputBytes(entry.bytes) ? ` \xB7 ${formatOutputBytes(entry.bytes)}` : "";
52524
+ return [`${entry.path}${suffix}`];
52525
+ });
52526
+ return activitySuccess(
52527
+ lines.length > 0 ? lines.join("\n") : "No entries.",
52528
+ value.truncated === true
52529
+ );
52530
+ }
52531
+ function searchTextActivityOutput(value) {
52532
+ if (!isRecord(value) || !Array.isArray(value.matches)) return void 0;
52533
+ const lines = value.matches.flatMap((match) => {
52534
+ if (!isRecord(match) || typeof match.path !== "string" || typeof match.line !== "number" || typeof match.column !== "number" || typeof match.text !== "string") return [];
52535
+ return [`${match.path}:${match.line}:${match.column} ${match.text}${match.lineTruncated === true ? " \u2026" : ""}`];
52536
+ });
52537
+ return activitySuccess(
52538
+ lines.length > 0 ? lines.join("\n") : "No matches.",
52539
+ value.truncated === true
52540
+ );
52541
+ }
52542
+ function activityOutput(job, result) {
52543
+ if (!result.ok) {
52544
+ return {
52545
+ kind: "error",
52546
+ ...boundedActivityPreview(result.error?.message ?? "The worker operation failed.")
52547
+ };
52548
+ }
52549
+ const value = result.value;
52550
+ switch (job.type) {
52551
+ case "read_file":
52552
+ if (isRecord(value) && typeof value.content === "string") {
52553
+ return activitySuccess(value.content || "(empty file)");
52554
+ }
52555
+ break;
52556
+ case "view_image":
52557
+ if (isRecord(value)) {
52558
+ const size = formatOutputBytes(value.bytes);
52559
+ const mimeType = typeof value.mimeType === "string" ? value.mimeType : "image";
52560
+ return activitySuccess(`Loaded ${mimeType}${size ? ` \xB7 ${size}` : ""}.`);
52561
+ }
52562
+ break;
52563
+ case "list_files": {
52564
+ const output = listFilesActivityOutput(value);
52565
+ if (output) return output;
52566
+ break;
52567
+ }
52568
+ case "search_text": {
52569
+ const output = searchTextActivityOutput(value);
52570
+ if (output) return output;
52571
+ break;
52572
+ }
52573
+ case "read_file_range":
52574
+ if (isRecord(value) && typeof value.content === "string") {
52575
+ return activitySuccess(value.content || "(empty range)", value.nextLine !== void 0);
52576
+ }
52577
+ break;
52578
+ case "write_file":
52579
+ if (isRecord(value)) {
52580
+ const size = formatOutputBytes(value.bytes);
52581
+ return activitySuccess(size ? `Wrote ${size}.` : "File written.");
52582
+ }
52583
+ break;
52584
+ case "edit_file":
52585
+ if (isRecord(value) && typeof value.diff === "string") {
52586
+ return activitySuccess(value.diff || "Edit applied.", value.diffTruncated === true);
52587
+ }
52588
+ break;
52589
+ case "make_directory":
52590
+ if (isRecord(value) && typeof value.created === "boolean") {
52591
+ return activitySuccess(value.created ? "Directory created." : "Directory already existed.");
52592
+ }
52593
+ break;
52594
+ case "delete_path":
52595
+ if (isRecord(value) && typeof value.deletedType === "string") {
52596
+ return activitySuccess(`Deleted ${value.deletedType}.`);
52597
+ }
52598
+ break;
52599
+ case "move_path":
52600
+ if (isRecord(value) && typeof value.movedType === "string") {
52601
+ return activitySuccess(`Moved ${value.movedType}.`);
52602
+ }
52603
+ break;
52604
+ case "run_command":
52605
+ case "get_command":
52606
+ case "cancel_command": {
52607
+ const output = commandActivityOutput(value);
52608
+ if (output) return output;
52609
+ break;
52610
+ }
52611
+ case "read_command_output":
52612
+ if (isRecord(value)) {
52613
+ const content = typeof value.content === "string" ? value.content : "";
52614
+ const sourceTruncated = value.nextOffset !== void 0 || value.retentionTruncated === true;
52615
+ return activitySuccess(content || "No output in this range.", sourceTruncated);
52616
+ }
52617
+ break;
52618
+ }
52619
+ return activitySuccess("Completed successfully.");
52620
+ }
52318
52621
  function activitySafeJob(job) {
52319
52622
  if (!containsRestrictedAuthenticationData(job)) return job;
52320
52623
  switch (job.type) {
@@ -52387,7 +52690,8 @@ function visibleWorker(worker, options) {
52387
52690
  type: "activity",
52388
52691
  phase: "returned",
52389
52692
  job: visibleJob,
52390
- ok: result.ok
52693
+ ok: result.ok,
52694
+ output: activityOutput(job, result)
52391
52695
  },
52392
52696
  `${activityResultLabel(job, result)} (${job.requestId}).`
52393
52697
  );
@@ -52399,7 +52703,13 @@ function visibleWorker(worker, options) {
52399
52703
  type: "activity",
52400
52704
  phase: "returned",
52401
52705
  job: visibleJob,
52402
- ok: false
52706
+ ok: false,
52707
+ output: {
52708
+ kind: "error",
52709
+ ...boundedActivityPreview(
52710
+ error46 instanceof Error ? error46.message : "The worker operation failed."
52711
+ )
52712
+ }
52403
52713
  },
52404
52714
  `${job.type} failed (${job.requestId}).`
52405
52715
  );
@@ -52453,7 +52763,9 @@ function statusMessage(status, connectedBefore) {
52453
52763
  }
52454
52764
  if (status.state === "retrying") {
52455
52765
  const prefix = connectedBefore ? "Connection lost" : "Could not connect";
52456
- return `${prefix}: ${status.error.message} ${retryMessage(status.retryInMs)}`;
52766
+ const message = status.error.message.trim();
52767
+ const reason = /[.!?]$/.test(message) ? message : `${message}.`;
52768
+ return `${prefix}: ${reason} ${retryMessage(status.retryInMs)}`;
52457
52769
  }
52458
52770
  return "Disconnected from Glossa.";
52459
52771
  }
@@ -52461,6 +52773,8 @@ async function connectRemoteWorker(endpoints, device, worker, options, signal, o
52461
52773
  let connectionState;
52462
52774
  let connectedBefore = false;
52463
52775
  let connectHintTask;
52776
+ let mcpContractTask = Promise.resolve();
52777
+ let observedMcpContractVersion;
52464
52778
  const remoteWorker = new RemoteWorker({
52465
52779
  origin: endpoints.workerOrigin,
52466
52780
  deviceToken: device.token,
@@ -52479,6 +52793,15 @@ async function connectRemoteWorker(endpoints, device, worker, options, signal, o
52479
52793
  } else {
52480
52794
  options.onEvent?.({ type: "status", status });
52481
52795
  }
52796
+ if (status.state === "connected" && options.workerVersion && status.mcpContractVersion && status.mcpContractVersion !== observedMcpContractVersion) {
52797
+ observedMcpContractVersion = status.mcpContractVersion;
52798
+ const mcpContractVersion = status.mcpContractVersion;
52799
+ mcpContractTask = mcpContractTask.then(async () => {
52800
+ if (!await observeMcpContractVersion(options.workerVersion, mcpContractVersion)) return;
52801
+ const message = "Glossa's ChatGPT tools changed. Refresh or rescan the Glossa app in ChatGPT before using new tools.";
52802
+ report(options, { type: "notice", message }, message);
52803
+ }).catch(() => void 0);
52804
+ }
52482
52805
  if (status.state === "connected" && !status.reconnected && shouldShowConnectHint(endpoints.relayOrigin) && !connectHintTask) {
52483
52806
  connectHintTask = announceConnectHint(
52484
52807
  connectHintStore(),
@@ -52498,6 +52821,7 @@ async function connectRemoteWorker(endpoints, device, worker, options, signal, o
52498
52821
  await remoteWorker.run();
52499
52822
  } finally {
52500
52823
  await connectHintTask;
52824
+ await mcpContractTask;
52501
52825
  }
52502
52826
  }
52503
52827
  async function runManagedSession(root, endpoints, options = {}) {
@@ -52561,18 +52885,455 @@ async function runManagedSession(root, endpoints, options = {}) {
52561
52885
  }
52562
52886
  }
52563
52887
 
52564
- // src/ui-hud-model.ts
52565
- function retainPostExitNotice(current, event) {
52566
- return event.type === "notice" && event.persistAfterExit ? event.message : current;
52888
+ // src/ui-hud-activity.ts
52889
+ var INLINE_DEFAULT_IGNORABLE = new RegExp("\\p{Default_Ignorable_Code_Point}", "u");
52890
+ function escapeCodePoint(codePoint) {
52891
+ const hexadecimal = codePoint.toString(16);
52892
+ return codePoint <= 65535 ? `\\u${hexadecimal.padStart(4, "0")}` : `\\u{${hexadecimal}}`;
52567
52893
  }
52568
- function initialHudState(workspace) {
52569
- return {
52894
+ function escapeActivityText(value, quote2 = false) {
52895
+ let escaped = "";
52896
+ for (const character of value) {
52897
+ const codePoint = character.codePointAt(0);
52898
+ if (character === "\\") escaped += "\\\\";
52899
+ else if (quote2 && character === '"') escaped += '\\"';
52900
+ else if (character === "\n") escaped += "\\n";
52901
+ else if (character === "\r") escaped += "\\r";
52902
+ else if (character === " ") escaped += "\\t";
52903
+ else if (character === "\b") escaped += "\\b";
52904
+ else if (character === "\f") escaped += "\\f";
52905
+ else if (codePoint < 32 || codePoint >= 127 && codePoint <= 159 || INLINE_DEFAULT_IGNORABLE.test(character) || codePoint === 8232 || codePoint === 8233) {
52906
+ escaped += escapeCodePoint(codePoint);
52907
+ } else escaped += character;
52908
+ }
52909
+ return escaped;
52910
+ }
52911
+ function quote(value) {
52912
+ return `"${escapeActivityText(value, true)}"`;
52913
+ }
52914
+ function formatByteCount(bytes) {
52915
+ if (bytes < 1024) return `${bytes} B`;
52916
+ const kibibytes = bytes / 1024;
52917
+ if (kibibytes < 1024) {
52918
+ return `${kibibytes.toFixed(kibibytes < 10 ? 1 : 0)} KiB`;
52919
+ }
52920
+ return `${(kibibytes / 1024).toFixed(1)} MiB`;
52921
+ }
52922
+ function truncateMiddle(value, width) {
52923
+ if (width <= 0) return "";
52924
+ if (value.length <= width) return value;
52925
+ if (width === 1) return "\u2026";
52926
+ const visible = width - 1;
52927
+ const leading = Math.max(1, Math.floor(visible * 0.45));
52928
+ const trailing = visible - leading;
52929
+ let start = value.slice(0, leading);
52930
+ if (/[\ud800-\udbff]$/.test(start)) start = start.slice(0, -1);
52931
+ let end = trailing > 0 ? value.slice(-trailing) : "";
52932
+ if (/^[\udc00-\udfff]/.test(end)) end = end.slice(1);
52933
+ return `${start}\u2026${end}`;
52934
+ }
52935
+ function fitPath(path12, width, quoted = false) {
52936
+ const wrapper = quoted ? 2 : 0;
52937
+ const innerWidth = Math.max(0, width - wrapper);
52938
+ const escaped = escapeActivityText(path12, quoted);
52939
+ if (escaped.length <= innerWidth) return quoted ? `"${escaped}"` : escaped;
52940
+ const slash = path12.includes("/") ? "/" : path12.includes("\\") ? "\\" : void 0;
52941
+ if (!slash) {
52942
+ const fitted2 = truncateMiddle(escaped, innerWidth);
52943
+ return quoted ? `"${fitted2}"` : fitted2;
52944
+ }
52945
+ const parts = path12.split(slash).map((part) => escapeActivityText(part, quoted));
52946
+ if (parts.length < 3) {
52947
+ const fitted2 = truncateMiddle(escaped, innerWidth);
52948
+ return quoted ? `"${fitted2}"` : fitted2;
52949
+ }
52950
+ const first = parts[0];
52951
+ const tail = [parts.at(-1)];
52952
+ for (let index = parts.length - 2; index > 0; index -= 1) {
52953
+ const candidateTail = [parts[index], ...tail];
52954
+ const candidate2 = `${first}${slash}\u2026${slash}${candidateTail.join(slash)}`;
52955
+ if (candidate2.length > innerWidth) break;
52956
+ tail.unshift(parts[index]);
52957
+ }
52958
+ const candidate = `${first}${slash}\u2026${slash}${tail.join(slash)}`;
52959
+ const filename = parts.at(-1);
52960
+ const collapsedFilename = `\u2026${slash}${filename}`;
52961
+ const fitted = candidate.length <= innerWidth ? candidate : collapsedFilename.length <= innerWidth ? collapsedFilename : truncateMiddle(filename, innerWidth);
52962
+ return quoted ? `"${fitted}"` : fitted;
52963
+ }
52964
+ function compactToken(value) {
52965
+ const escaped = escapeActivityText(value, true);
52966
+ return /^[A-Za-z0-9_@./:=+,-]+$/.test(escaped) && escaped.length > 0 ? escaped : `"${escaped}"`;
52967
+ }
52968
+ function fitToken(token, width) {
52969
+ if (width <= 0) return "";
52970
+ if (token.length <= width) return token;
52971
+ if (token.startsWith('"') && token.endsWith('"') && width >= 3) {
52972
+ return `"${truncateMiddle(token.slice(1, -1), width - 2)}"`;
52973
+ }
52974
+ return truncateMiddle(token, width);
52975
+ }
52976
+ function fitDelimitedTokens(prefix, tokens, suffix, width, separator) {
52977
+ const full = `${prefix}${tokens.join(separator)}${suffix}`;
52978
+ if (full.length <= width || tokens.length <= 2) return truncateMiddle(full, width);
52979
+ const left = [tokens[0]];
52980
+ const right = [tokens.at(-1)];
52981
+ let leftIndex = 1;
52982
+ let rightIndex = tokens.length - 2;
52983
+ while (leftIndex <= rightIndex) {
52984
+ let changed = false;
52985
+ const withLeft = `${prefix}${[...left, tokens[leftIndex], "\u2026", ...right].join(separator)}${suffix}`;
52986
+ if (withLeft.length <= width) {
52987
+ left.push(tokens[leftIndex]);
52988
+ leftIndex += 1;
52989
+ changed = true;
52990
+ }
52991
+ if (leftIndex > rightIndex) break;
52992
+ const withRight = `${prefix}${[...left, "\u2026", tokens[rightIndex], ...right].join(separator)}${suffix}`;
52993
+ if (withRight.length <= width) {
52994
+ right.unshift(tokens[rightIndex]);
52995
+ rightIndex -= 1;
52996
+ changed = true;
52997
+ }
52998
+ if (!changed) break;
52999
+ }
53000
+ const candidate = `${prefix}${[...left, "\u2026", ...right].join(separator)}${suffix}`;
53001
+ if (candidate.length <= width) return candidate;
53002
+ const minimalOverhead = prefix.length + suffix.length + separator.length * 2 + 1;
53003
+ const available = width - minimalOverhead;
53004
+ if (available < 2) {
53005
+ const structural = `${prefix}\u2026${suffix}`;
53006
+ return structural.length <= width ? structural : truncateMiddle(structural, width);
53007
+ }
53008
+ const first = tokens[0];
53009
+ const last = tokens.at(-1);
53010
+ let firstWidth = Math.min(first.length, Math.max(1, Math.floor(available / 2)));
53011
+ let lastWidth = available - firstWidth;
53012
+ if (last.length < lastWidth) {
53013
+ lastWidth = last.length;
53014
+ firstWidth = available - lastWidth;
53015
+ } else if (first.length < firstWidth) {
53016
+ firstWidth = first.length;
53017
+ lastWidth = available - firstWidth;
53018
+ }
53019
+ return `${prefix}${fitToken(first, firstWidth)}${separator}\u2026${separator}${fitToken(last, lastWidth)}${suffix}`;
53020
+ }
53021
+ function compactArgv(argv, width) {
53022
+ return fitDelimitedTokens("", argv.map(compactToken), "", width, " ");
53023
+ }
53024
+ function detailedArgv(argv, width) {
53025
+ return fitDelimitedTokens("argv [", argv.map(quote), "]", width, ", ");
53026
+ }
53027
+ function shortId(value) {
53028
+ return value.length <= 18 ? value : `${value.slice(0, 8)}\u2026${value.slice(-4)}`;
53029
+ }
53030
+ function appendDetails(target, details, width) {
53031
+ let result = target;
53032
+ for (const detail of details) {
53033
+ const candidate = `${result} \xB7 ${detail}`;
53034
+ if (candidate.length > width) continue;
53035
+ result = candidate;
53036
+ }
53037
+ return truncateMiddle(result, width);
53038
+ }
53039
+ function lineRange(call) {
53040
+ if (call.startLine && call.lineCount) return `lines ${call.startLine}\u2013${call.startLine + call.lineCount - 1}`;
53041
+ if (call.startLine) return `from line ${call.startLine}`;
53042
+ if (call.lineCount) return `first ${call.lineCount} lines`;
53043
+ return void 0;
53044
+ }
53045
+ function activityCallFromJob(job) {
53046
+ switch (job.type) {
53047
+ case "read_file":
53048
+ case "view_image":
53049
+ return { type: job.type, path: job.path };
53050
+ case "list_files":
53051
+ return {
53052
+ type: job.type,
53053
+ ...job.path === void 0 ? {} : { path: job.path },
53054
+ ...job.recursive === void 0 ? {} : { recursive: job.recursive },
53055
+ ...job.cursor === void 0 ? {} : { cursor: job.cursor },
53056
+ ...job.limit === void 0 ? {} : { limit: job.limit },
53057
+ timeoutMs: job.timeoutMs
53058
+ };
53059
+ case "search_text":
53060
+ return {
53061
+ type: job.type,
53062
+ query: job.query,
53063
+ ...job.path === void 0 ? {} : { path: job.path },
53064
+ ...job.matchMode === void 0 ? {} : { matchMode: job.matchMode },
53065
+ ...job.caseSensitive === void 0 ? {} : { caseSensitive: job.caseSensitive },
53066
+ ...job.maxResults === void 0 ? {} : { maxResults: job.maxResults },
53067
+ ...job.extensions === void 0 ? {} : { extensions: job.extensions },
53068
+ ...job.includeGlobs === void 0 ? {} : { includeGlobs: job.includeGlobs },
53069
+ ...job.excludeGlobs === void 0 ? {} : { excludeGlobs: job.excludeGlobs },
53070
+ timeoutMs: job.timeoutMs
53071
+ };
53072
+ case "read_file_range":
53073
+ return {
53074
+ type: job.type,
53075
+ path: job.path,
53076
+ ...job.startLine === void 0 ? {} : { startLine: job.startLine },
53077
+ ...job.lineCount === void 0 ? {} : { lineCount: job.lineCount },
53078
+ timeoutMs: job.timeoutMs
53079
+ };
53080
+ case "write_file":
53081
+ return {
53082
+ type: job.type,
53083
+ path: job.path,
53084
+ contentBytes: Buffer.byteLength(job.content, "utf8"),
53085
+ ...job.expectedSha256 === void 0 ? {} : { expectedSha256: job.expectedSha256 }
53086
+ };
53087
+ case "edit_file":
53088
+ return {
53089
+ type: job.type,
53090
+ path: job.path,
53091
+ editCount: job.edits.length,
53092
+ editBytes: job.edits.reduce(
53093
+ (total, edit) => total + Buffer.byteLength(edit.oldText, "utf8") + Buffer.byteLength(edit.newText, "utf8"),
53094
+ 0
53095
+ ),
53096
+ ...job.expectedSha256 === void 0 ? {} : { expectedSha256: job.expectedSha256 }
53097
+ };
53098
+ case "make_directory":
53099
+ case "delete_path":
53100
+ return {
53101
+ type: job.type,
53102
+ path: job.path,
53103
+ ...job.recursive === void 0 ? {} : { recursive: job.recursive }
53104
+ };
53105
+ case "move_path":
53106
+ return { type: job.type, source: job.source, destination: job.destination };
53107
+ case "run_command":
53108
+ return {
53109
+ type: job.type,
53110
+ ...job.argv === void 0 ? {} : { argv: job.argv },
53111
+ ...job.shellCommand === void 0 ? {} : { shellCommand: job.shellCommand },
53112
+ ...job.stdin === void 0 ? {} : { stdinBytes: Buffer.byteLength(job.stdin, "utf8") },
53113
+ timeoutMs: job.timeoutMs,
53114
+ ...job.waitMs === void 0 ? {} : { waitMs: job.waitMs }
53115
+ };
53116
+ case "get_command":
53117
+ return {
53118
+ type: job.type,
53119
+ commandId: job.commandId,
53120
+ ...job.waitMs === void 0 ? {} : { waitMs: job.waitMs },
53121
+ ...job.afterSequence === void 0 ? {} : { afterSequence: job.afterSequence }
53122
+ };
53123
+ case "read_command_output":
53124
+ return {
53125
+ type: job.type,
53126
+ commandId: job.commandId,
53127
+ stream: job.stream,
53128
+ ...job.offset === void 0 ? {} : { offset: job.offset },
53129
+ ...job.maxBytes === void 0 ? {} : { maxBytes: job.maxBytes }
53130
+ };
53131
+ case "cancel_command":
53132
+ return { type: job.type, commandId: job.commandId };
53133
+ }
53134
+ }
53135
+ function activityCallByteLength(call) {
53136
+ return Buffer.byteLength(JSON.stringify(call), "utf8");
53137
+ }
53138
+ function formatActivityCall(call, mode, width) {
53139
+ const detailed = mode === "detailed";
53140
+ const pathWidth = detailed ? Math.max(1, width - 5) : width;
53141
+ switch (call.type) {
53142
+ case "read_file":
53143
+ case "view_image":
53144
+ return detailed ? `path ${fitPath(call.path, pathWidth, true)}` : fitPath(call.path, width);
53145
+ case "list_files": {
53146
+ const target = detailed ? `path ${fitPath(call.path ?? ".", pathWidth, true)}` : fitPath(call.path ?? ".", width);
53147
+ const details = detailed ? [
53148
+ ...call.recursive ? ["recursive"] : [],
53149
+ ...call.limit ? [`limit ${call.limit}`] : [],
53150
+ ...call.cursor ? [`after ${truncateMiddle(quote(call.cursor), 28)}`] : [],
53151
+ ...call.timeoutMs === MAX_STRUCTURED_READ_TIMEOUT_MS ? [] : [`timeout ${call.timeoutMs} ms`]
53152
+ ] : call.recursive ? ["recursive"] : [];
53153
+ return appendDetails(target, details, width);
53154
+ }
53155
+ case "search_text": {
53156
+ const queryPrefix = detailed ? "query " : "";
53157
+ const connector = detailed ? " in path " : " in ";
53158
+ const contentWidth = width - queryPrefix.length - connector.length;
53159
+ if (contentWidth < 6) {
53160
+ return `${queryPrefix}${fitToken(quote(call.query), Math.max(1, width - queryPrefix.length))}`;
53161
+ }
53162
+ let pathBudget = Math.max(3, Math.floor(contentWidth * 0.42));
53163
+ let path12 = fitPath(call.path ?? ".", pathBudget, detailed);
53164
+ let queryWidth = contentWidth - path12.length;
53165
+ if (queryWidth < 3) {
53166
+ pathBudget = Math.max(3, pathBudget - (3 - queryWidth));
53167
+ path12 = fitPath(call.path ?? ".", pathBudget, detailed);
53168
+ queryWidth = contentWidth - path12.length;
53169
+ }
53170
+ const query = fitToken(quote(call.query), Math.max(1, queryWidth));
53171
+ const target = `${queryPrefix}${query}${connector}${path12}`;
53172
+ const details = detailed ? [
53173
+ ...call.matchMode === "regex" ? ["regex"] : [],
53174
+ ...call.caseSensitive ? ["case-sensitive"] : [],
53175
+ ...call.extensions?.length ? [`extensions ${call.extensions.join(", ")}`] : [],
53176
+ ...call.includeGlobs?.length ? [`${call.includeGlobs.length} include ${call.includeGlobs.length === 1 ? "glob" : "globs"}`] : [],
53177
+ ...call.excludeGlobs?.length ? [`${call.excludeGlobs.length} exclude ${call.excludeGlobs.length === 1 ? "glob" : "globs"}`] : [],
53178
+ ...call.maxResults ? [`limit ${call.maxResults}`] : [],
53179
+ ...call.timeoutMs === MAX_STRUCTURED_READ_TIMEOUT_MS ? [] : [`timeout ${call.timeoutMs} ms`]
53180
+ ] : [];
53181
+ return appendDetails(target, details, width);
53182
+ }
53183
+ case "read_file_range": {
53184
+ const range = lineRange(call);
53185
+ const rangeWidth = range ? range.length + 3 : 0;
53186
+ const target = detailed ? `path ${fitPath(call.path, Math.max(8, pathWidth - rangeWidth), true)}` : fitPath(call.path, Math.max(8, width - rangeWidth));
53187
+ return appendDetails(target, [range, detailed && call.timeoutMs !== MAX_STRUCTURED_READ_TIMEOUT_MS ? `timeout ${call.timeoutMs} ms` : void 0].filter((value) => Boolean(value)), width);
53188
+ }
53189
+ case "write_file": {
53190
+ const target = detailed ? `path ${fitPath(call.path, pathWidth, true)}` : fitPath(call.path, width);
53191
+ return appendDetails(target, [formatByteCount(call.contentBytes), ...call.expectedSha256 ? ["guarded"] : []], width);
53192
+ }
53193
+ case "edit_file": {
53194
+ const target = detailed ? `path ${fitPath(call.path, pathWidth, true)}` : fitPath(call.path, width);
53195
+ const editLabel = `${call.editCount} ${call.editCount === 1 ? "edit" : "edits"}`;
53196
+ return appendDetails(target, [editLabel, ...call.expectedSha256 ? ["guarded"] : [], ...detailed ? [formatByteCount(call.editBytes)] : []], width);
53197
+ }
53198
+ case "make_directory":
53199
+ case "delete_path": {
53200
+ const target = detailed ? `path ${fitPath(call.path, pathWidth, true)}` : fitPath(call.path, width);
53201
+ return appendDetails(target, call.recursive ? ["recursive"] : [], width);
53202
+ }
53203
+ case "move_path": {
53204
+ const leftWidth = Math.max(4, Math.floor((width - 3) / 2));
53205
+ const rightWidth = Math.max(4, width - leftWidth - 3);
53206
+ return `${fitPath(call.source, leftWidth, detailed)} \u2192 ${fitPath(call.destination, rightWidth, detailed)}`;
53207
+ }
53208
+ case "run_command": {
53209
+ const target = call.argv ? detailed ? detailedArgv(call.argv, width) : compactArgv(call.argv, width) : detailed ? `shell ${truncateMiddle(quote(call.shellCommand ?? ""), Math.max(1, width - 6))}` : truncateMiddle(escapeActivityText(call.shellCommand ?? ""), width);
53210
+ if (!detailed) return target;
53211
+ return appendDetails(target, [
53212
+ ...call.stdinBytes === void 0 ? [] : [`stdin ${formatByteCount(call.stdinBytes)}`],
53213
+ ...call.timeoutMs === DEFAULT_COMMAND_TIMEOUT_MS ? [] : [`timeout ${call.timeoutMs} ms`],
53214
+ ...call.waitMs === void 0 || call.waitMs === DEFAULT_COMMAND_FAST_WAIT_MS ? [] : [`wait ${call.waitMs} ms`]
53215
+ ], width);
53216
+ }
53217
+ case "get_command": {
53218
+ const target = `command ${detailed ? call.commandId : shortId(call.commandId)}`;
53219
+ return detailed ? appendDetails(target, [
53220
+ ...call.waitMs ? [`wait ${call.waitMs} ms`] : [],
53221
+ ...call.afterSequence === void 0 ? [] : [`after sequence ${call.afterSequence}`]
53222
+ ], width) : truncateMiddle(target, width);
53223
+ }
53224
+ case "read_command_output": {
53225
+ const target = `command ${detailed ? call.commandId : shortId(call.commandId)} ${call.stream}`;
53226
+ return detailed ? appendDetails(target, [
53227
+ ...call.offset === void 0 ? [] : [`offset ${call.offset}`],
53228
+ ...call.maxBytes === void 0 ? [] : [`max ${call.maxBytes} bytes`]
53229
+ ], width) : truncateMiddle(target, width);
53230
+ }
53231
+ case "cancel_command":
53232
+ return truncateMiddle(`command ${detailed ? call.commandId : shortId(call.commandId)}`, width);
53233
+ }
53234
+ }
53235
+ function listValue(values) {
53236
+ return `[${values.map(quote).join(", ")}]`;
53237
+ }
53238
+ function activityCallDetailFields(call) {
53239
+ switch (call.type) {
53240
+ case "read_file":
53241
+ case "view_image":
53242
+ return [{ label: "path", value: quote(call.path) }];
53243
+ case "list_files":
53244
+ return [
53245
+ { label: "path", value: call.path === void 0 ? '"."' : quote(call.path) },
53246
+ ...call.recursive ? [{ label: "recursive", value: "true" }] : [],
53247
+ ...call.cursor === void 0 ? [] : [{ label: "cursor", value: quote(call.cursor) }],
53248
+ ...call.limit === void 0 ? [] : [{ label: "limit", value: String(call.limit) }],
53249
+ ...call.timeoutMs === MAX_STRUCTURED_READ_TIMEOUT_MS ? [] : [{ label: "timeoutMs", value: String(call.timeoutMs) }]
53250
+ ];
53251
+ case "search_text":
53252
+ return [
53253
+ { label: "query", value: quote(call.query) },
53254
+ { label: "path", value: call.path === void 0 ? '"."' : quote(call.path) },
53255
+ ...call.matchMode === "regex" ? [{ label: "matchMode", value: "regex" }] : [],
53256
+ ...call.caseSensitive ? [{ label: "caseSensitive", value: "true" }] : [],
53257
+ ...call.maxResults === void 0 ? [] : [{ label: "maxResults", value: String(call.maxResults) }],
53258
+ ...call.extensions === void 0 ? [] : [{ label: "extensions", value: listValue(call.extensions) }],
53259
+ ...call.includeGlobs === void 0 ? [] : [{ label: "includeGlobs", value: listValue(call.includeGlobs) }],
53260
+ ...call.excludeGlobs === void 0 ? [] : [{ label: "excludeGlobs", value: listValue(call.excludeGlobs) }],
53261
+ ...call.timeoutMs === MAX_STRUCTURED_READ_TIMEOUT_MS ? [] : [{ label: "timeoutMs", value: String(call.timeoutMs) }]
53262
+ ];
53263
+ case "read_file_range":
53264
+ return [
53265
+ { label: "path", value: quote(call.path) },
53266
+ ...call.startLine === void 0 ? [] : [{ label: "startLine", value: String(call.startLine) }],
53267
+ ...call.lineCount === void 0 ? [] : [{ label: "lineCount", value: String(call.lineCount) }],
53268
+ ...call.timeoutMs === MAX_STRUCTURED_READ_TIMEOUT_MS ? [] : [{ label: "timeoutMs", value: String(call.timeoutMs) }]
53269
+ ];
53270
+ case "write_file":
53271
+ return [
53272
+ { label: "path", value: quote(call.path) },
53273
+ { label: "content", value: `${formatByteCount(call.contentBytes)} \xB7 content not retained in Activity` },
53274
+ ...call.expectedSha256 === void 0 ? [] : [{ label: "expectedSha256", value: call.expectedSha256 }]
53275
+ ];
53276
+ case "edit_file":
53277
+ return [
53278
+ { label: "path", value: quote(call.path) },
53279
+ { label: "edits", value: `${call.editCount} ${call.editCount === 1 ? "edit" : "edits"} \xB7 ${formatByteCount(call.editBytes)} \xB7 text not retained in Activity` },
53280
+ ...call.expectedSha256 === void 0 ? [] : [{ label: "expectedSha256", value: call.expectedSha256 }]
53281
+ ];
53282
+ case "make_directory":
53283
+ case "delete_path":
53284
+ return [
53285
+ { label: "path", value: quote(call.path) },
53286
+ ...call.recursive ? [{ label: "recursive", value: "true" }] : []
53287
+ ];
53288
+ case "move_path":
53289
+ return [
53290
+ { label: "source", value: quote(call.source) },
53291
+ { label: "destination", value: quote(call.destination) }
53292
+ ];
53293
+ case "run_command":
53294
+ return [
53295
+ ...call.argv === void 0 ? [] : [{ label: "argv", value: listValue(call.argv) }],
53296
+ ...call.shellCommand === void 0 ? [] : [{ label: "shellCommand", value: quote(call.shellCommand) }],
53297
+ ...call.stdinBytes === void 0 ? [] : [{ label: "stdin", value: `${formatByteCount(call.stdinBytes)} \xB7 content not retained in Activity` }],
53298
+ ...call.timeoutMs === DEFAULT_COMMAND_TIMEOUT_MS ? [] : [{ label: "timeoutMs", value: String(call.timeoutMs) }],
53299
+ ...call.waitMs === void 0 || call.waitMs === DEFAULT_COMMAND_FAST_WAIT_MS ? [] : [{ label: "waitMs", value: String(call.waitMs) }]
53300
+ ];
53301
+ case "get_command":
53302
+ return [
53303
+ { label: "commandId", value: call.commandId },
53304
+ ...call.waitMs === void 0 ? [] : [{ label: "waitMs", value: String(call.waitMs) }],
53305
+ ...call.afterSequence === void 0 ? [] : [{ label: "afterSequence", value: String(call.afterSequence) }]
53306
+ ];
53307
+ case "read_command_output":
53308
+ return [
53309
+ { label: "commandId", value: call.commandId },
53310
+ { label: "stream", value: call.stream },
53311
+ ...call.offset === void 0 || call.offset === 0 ? [] : [{ label: "offset", value: String(call.offset) }],
53312
+ ...call.maxBytes === void 0 || call.maxBytes === DEFAULT_COMMAND_OUTPUT_RANGE_BYTES ? [] : [{ label: "maxBytes", value: String(call.maxBytes) }]
53313
+ ];
53314
+ case "cancel_command":
53315
+ return [{ label: "commandId", value: call.commandId }];
53316
+ }
53317
+ }
53318
+ function activityToolTitle(type) {
53319
+ return type.split("_").map((part) => part[0].toUpperCase() + part.slice(1)).join(" ");
53320
+ }
53321
+
53322
+ // src/ui-hud-model.ts
53323
+ function retainPostExitNotice(current, event) {
53324
+ return event.type === "notice" && event.persistAfterExit ? event.message : current;
53325
+ }
53326
+ function initialHudState(workspace) {
53327
+ return {
52570
53328
  workspace,
52571
53329
  connection: "starting",
52572
53330
  connectedBefore: false,
52573
53331
  message: void 0,
52574
53332
  activities: [],
52575
- activityPage: 0,
53333
+ activityMode: "compact",
53334
+ activitySelection: void 0,
53335
+ activityBrowseAnchor: void 0,
53336
+ activityDetailScroll: 0,
52576
53337
  view: "workspace",
52577
53338
  status: void 0,
52578
53339
  deviceSelection: 0,
@@ -52580,18 +53341,21 @@ function initialHudState(workspace) {
52580
53341
  statusLoading: false,
52581
53342
  prompt: void 0,
52582
53343
  busy: false,
52583
- notice: void 0
53344
+ busyMessage: void 0,
53345
+ notice: void 0,
53346
+ noticeTone: void 0
52584
53347
  };
52585
53348
  }
52586
53349
  var MAX_STORED_ACTIVITIES = 9999;
52587
53350
  var MAX_STORED_ACTIVITY_TARGET_CHARS = 512;
53351
+ var MAX_RETAINED_ACTIVITY_CALL_BYTES = 16 * 1024 * 1024;
52588
53352
  function truncate(value, width) {
52589
53353
  if (width <= 0) return "";
52590
53354
  if (value.length <= width) return value;
52591
53355
  if (width === 1) return "\u2026";
52592
53356
  return `${value.slice(0, width - 1)}\u2026`;
52593
53357
  }
52594
- function truncateMiddle(value, width) {
53358
+ function truncateMiddle2(value, width) {
52595
53359
  if (width <= 0) return "";
52596
53360
  if (value.length <= width) return value;
52597
53361
  if (width === 1) return "\u2026";
@@ -52604,24 +53368,24 @@ function truncateMiddle(value, width) {
52604
53368
  if (/^[\udc00-\udfff]/.test(end)) end = end.slice(1);
52605
53369
  return `${start}\u2026${end}`;
52606
53370
  }
52607
- var INLINE_DEFAULT_IGNORABLE = new RegExp("\\p{Default_Ignorable_Code_Point}", "u");
52608
- function escapeCodePoint(codePoint) {
53371
+ var INLINE_DEFAULT_IGNORABLE2 = new RegExp("\\p{Default_Ignorable_Code_Point}", "u");
53372
+ function escapeCodePoint2(codePoint) {
52609
53373
  const hexadecimal = codePoint.toString(16);
52610
53374
  return codePoint <= 65535 ? `\\u${hexadecimal.padStart(4, "0")}` : `\\u{${hexadecimal}}`;
52611
53375
  }
52612
- function escapeInline(value, quote = false) {
53376
+ function escapeInline(value, quote2 = false) {
52613
53377
  let escaped = "";
52614
53378
  for (const character of value) {
52615
53379
  const codePoint = character.codePointAt(0);
52616
53380
  if (character === "\\") escaped += "\\\\";
52617
- else if (quote && character === '"') escaped += '\\"';
53381
+ else if (quote2 && character === '"') escaped += '\\"';
52618
53382
  else if (character === "\n") escaped += "\\n";
52619
53383
  else if (character === "\r") escaped += "\\r";
52620
53384
  else if (character === " ") escaped += "\\t";
52621
53385
  else if (character === "\b") escaped += "\\b";
52622
53386
  else if (character === "\f") escaped += "\\f";
52623
- else if (codePoint < 32 || codePoint >= 127 && codePoint <= 159 || INLINE_DEFAULT_IGNORABLE.test(character) || codePoint === 8232 || codePoint === 8233) {
52624
- escaped += escapeCodePoint(codePoint);
53387
+ else if (codePoint < 32 || codePoint >= 127 && codePoint <= 159 || INLINE_DEFAULT_IGNORABLE2.test(character) || codePoint === 8232 || codePoint === 8233) {
53388
+ escaped += escapeCodePoint2(codePoint);
52625
53389
  } else escaped += character;
52626
53390
  }
52627
53391
  return escaped;
@@ -52630,12 +53394,12 @@ function quoteInline(value) {
52630
53394
  return `"${escapeInline(value, true)}"`;
52631
53395
  }
52632
53396
  function boundInlineInput(value) {
52633
- return truncateMiddle(value, MAX_STORED_ACTIVITY_TARGET_CHARS);
53397
+ return truncateMiddle2(value, MAX_STORED_ACTIVITY_TARGET_CHARS);
52634
53398
  }
52635
53399
  function quoteActivityInput(value) {
52636
53400
  return quoteInline(boundInlineInput(value));
52637
53401
  }
52638
- function formatByteCount(value) {
53402
+ function formatByteCount2(value) {
52639
53403
  const bytes = Buffer.byteLength(value, "utf8");
52640
53404
  if (bytes < 1024) return `${bytes} B`;
52641
53405
  const kibibytes = bytes / 1024;
@@ -52699,7 +53463,7 @@ function summarizeJob(job) {
52699
53463
  }
52700
53464
  case "write_file":
52701
53465
  return pathSummary(job.path, [
52702
- formatByteCount(job.content),
53466
+ formatByteCount2(job.content),
52703
53467
  ...job.expectedSha256 ? ["guarded"] : []
52704
53468
  ]);
52705
53469
  case "edit_file":
@@ -52721,7 +53485,7 @@ function summarizeJob(job) {
52721
53485
  return {
52722
53486
  target: job.argv ? `argv [${job.argv.map(quoteActivityInput).join(", ")}]` : `shell ${quoteActivityInput(job.shellCommand ?? "")}`,
52723
53487
  details: [
52724
- ...job.stdin === void 0 ? [] : [`stdin ${formatByteCount(job.stdin)}`],
53488
+ ...job.stdin === void 0 ? [] : [`stdin ${formatByteCount2(job.stdin)}`],
52725
53489
  ...job.timeoutMs === DEFAULT_COMMAND_TIMEOUT_MS ? [] : [`timeout ${job.timeoutMs} ms`],
52726
53490
  ...job.waitMs === void 0 || job.waitMs === DEFAULT_COMMAND_FAST_WAIT_MS ? [] : [`wait ${job.waitMs} ms`]
52727
53491
  ],
@@ -52766,9 +53530,9 @@ function fitTargetSegments(segments, width) {
52766
53530
  const balancedTrailingWidth = available - balancedLeadingWidth;
52767
53531
  const leadingWidth = leading.length <= balancedLeadingWidth ? leading.length : trailing.length <= balancedTrailingWidth ? available - trailing.length : balancedLeadingWidth;
52768
53532
  return [
52769
- truncateMiddle(leading, leadingWidth),
53533
+ truncateMiddle2(leading, leadingWidth),
52770
53534
  separator,
52771
- truncateMiddle(trailing, available - leadingWidth)
53535
+ truncateMiddle2(trailing, available - leadingWidth)
52772
53536
  ];
52773
53537
  }
52774
53538
  function boundActivitySummary(summary) {
@@ -52779,8 +53543,28 @@ function boundActivitySummary(summary) {
52779
53543
  return {
52780
53544
  ...summary,
52781
53545
  ...targetSegments ? { targetSegments } : {},
52782
- target: targetSegments ? targetSegments.join("") : summary.truncation === "middle" ? truncateMiddle(summary.target, MAX_STORED_ACTIVITY_TARGET_CHARS) : truncate(summary.target, MAX_STORED_ACTIVITY_TARGET_CHARS)
52783
- };
53546
+ target: targetSegments ? targetSegments.join("") : summary.truncation === "middle" ? truncateMiddle2(summary.target, MAX_STORED_ACTIVITY_TARGET_CHARS) : truncate(summary.target, MAX_STORED_ACTIVITY_TARGET_CHARS)
53547
+ };
53548
+ }
53549
+ function pruneActivityCalls(activities) {
53550
+ let retainedBytes2 = 0;
53551
+ const next = [...activities];
53552
+ for (let index = next.length - 1; index >= 0; index -= 1) {
53553
+ const activity = next[index];
53554
+ if (!activity.call || activity.callBytes === void 0) continue;
53555
+ if (activity.callBytes > MAX_RETAINED_ACTIVITY_CALL_BYTES) {
53556
+ const { call: _call, callBytes: _callBytes, ...summaryOnly } = activity;
53557
+ next[index] = { ...summaryOnly, callUnavailable: "oversized" };
53558
+ continue;
53559
+ }
53560
+ if (retainedBytes2 + activity.callBytes > MAX_RETAINED_ACTIVITY_CALL_BYTES) {
53561
+ const { call: _call, callBytes: _callBytes, ...summaryOnly } = activity;
53562
+ next[index] = { ...summaryOnly, callUnavailable: "expired" };
53563
+ continue;
53564
+ }
53565
+ retainedBytes2 += activity.callBytes;
53566
+ }
53567
+ return next;
52784
53568
  }
52785
53569
  function applyHudEvent(state, event) {
52786
53570
  if (event.type === "session") {
@@ -52789,19 +53573,33 @@ function applyHudEvent(state, event) {
52789
53573
  ...state,
52790
53574
  workspace: event.root,
52791
53575
  deviceName: event.deviceName,
52792
- accessProfile: event.accessProfile,
53576
+ ...accessHandoff && state.accessProfile ? { accessProfile: state.accessProfile } : { accessProfile: event.accessProfile },
52793
53577
  pendingAccessProfile: accessHandoff ? state.pendingAccessProfile : void 0
52794
53578
  };
52795
53579
  }
52796
53580
  if (event.type === "status") {
52797
53581
  if (state.pendingAccessProfile && state.connectedBefore) {
52798
- if (event.status.state !== "connected") return state;
53582
+ if (event.status.state === "connected") {
53583
+ return {
53584
+ ...state,
53585
+ connection: "connected",
53586
+ connectedBefore: true,
53587
+ message: void 0,
53588
+ accessProfile: state.pendingAccessProfile,
53589
+ pendingAccessProfile: void 0
53590
+ };
53591
+ }
53592
+ if (event.status.state === "retrying") {
53593
+ return {
53594
+ ...state,
53595
+ connection: "retrying",
53596
+ message: statusMessage(event.status, true)
53597
+ };
53598
+ }
52799
53599
  return {
52800
53600
  ...state,
52801
- connection: "connected",
52802
- connectedBefore: true,
52803
- message: void 0,
52804
- pendingAccessProfile: void 0
53601
+ connection: event.status.state,
53602
+ message: event.status.state === "disconnected" ? statusMessage(event.status, true) : void 0
52805
53603
  };
52806
53604
  }
52807
53605
  if (event.status.state === "retrying") {
@@ -52819,26 +53617,46 @@ function applyHudEvent(state, event) {
52819
53617
  };
52820
53618
  }
52821
53619
  if (event.type === "notice") {
52822
- return { ...state, notice: event.message };
53620
+ return { ...state, notice: event.message, noticeTone: "info" };
52823
53621
  }
52824
53622
  const requestId = event.job.requestId;
52825
53623
  const existingIndex = state.activities.findIndex(
52826
53624
  (activity2) => activity2.requestId === requestId
52827
53625
  );
53626
+ const existing = existingIndex >= 0 ? state.activities[existingIndex] : void 0;
52828
53627
  const activityTimestamp = Date.now();
53628
+ const freshCall = existing?.callUnavailable ? void 0 : existing?.call ?? activityCallFromJob(event.job);
53629
+ const freshCallBytes = existing?.callBytes ?? (freshCall ? activityCallByteLength(freshCall) : void 0);
53630
+ const retainFreshCall = freshCall !== void 0 && freshCallBytes !== void 0 && freshCallBytes <= MAX_RETAINED_ACTIVITY_CALL_BYTES;
53631
+ const formatCall = freshCall ?? activityCallFromJob(event.job);
53632
+ const eventOutput = event.phase === "returned" ? event.output ?? {
53633
+ kind: event.ok ? "success" : "error"
53634
+ } : void 0;
52829
53635
  const activity = {
52830
53636
  tool: event.job.type,
52831
53637
  summary: boundActivitySummary(summarizeJob(event.job)),
53638
+ compactSummary: existing?.compactSummary ?? formatActivityCall(
53639
+ formatCall,
53640
+ "compact",
53641
+ MAX_STORED_ACTIVITY_TARGET_CHARS
53642
+ ),
53643
+ ...retainFreshCall ? { call: freshCall, callBytes: freshCallBytes } : {},
53644
+ ...!retainFreshCall && !existing?.callUnavailable ? { callUnavailable: "oversized" } : existing?.callUnavailable ? { callUnavailable: existing.callUnavailable } : {},
53645
+ ...eventOutput ? { output: eventOutput } : {},
52832
53646
  requestId,
52833
- state: event.phase === "started" ? "working" : event.ok ? "returned" : "failed",
53647
+ state: event.phase === "started" ? "working" : eventOutput?.kind === "error" ? "failed" : "returned",
53648
+ startedAt: existing?.startedAt ?? activityTimestamp,
52834
53649
  updatedAt: activityTimestamp
52835
53650
  };
52836
53651
  const activities = [...state.activities];
52837
53652
  if (existingIndex >= 0) activities[existingIndex] = activity;
52838
53653
  else activities.push(activity);
53654
+ const boundedActivities = pruneActivityCalls(activities.slice(-MAX_STORED_ACTIVITIES));
53655
+ const activitySelection = state.activitySelection && boundedActivities.some((item) => item.requestId === state.activitySelection) ? state.activitySelection : void 0;
52839
53656
  return {
52840
53657
  ...state,
52841
- activities: activities.slice(-MAX_STORED_ACTIVITIES)
53658
+ activities: boundedActivities,
53659
+ activitySelection
52842
53660
  };
52843
53661
  }
52844
53662
 
@@ -52856,7 +53674,9 @@ var COLORS = {
52856
53674
  function Text2({ color, ...props }) {
52857
53675
  return color === void 0 ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Text, { ...props }) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Text, { ...props, color });
52858
53676
  }
52859
- var ACTIVITY_REFRESH_INTERVAL_MS = 1e4;
53677
+ var ACTIVITY_IDLE_REFRESH_INTERVAL_MS = 1e4;
53678
+ var ACTIVITY_LIVE_REFRESH_INTERVAL_MS = 1e3;
53679
+ var ACTIVITY_SELECTOR_COLUMN_WIDTH = 2;
52860
53680
  var ACTIVITY_STATUS_COLUMN_WIDTH = 2;
52861
53681
  var ACTIVITY_TOOL_COLUMN_WIDTH = 15;
52862
53682
  var ACTIVITY_AGE_COLUMN_WIDTH = 10;
@@ -52864,8 +53684,11 @@ var ACTIVITY_PREAMBLE_LINES = 1;
52864
53684
  var HudStore = class {
52865
53685
  #state;
52866
53686
  #listeners = /* @__PURE__ */ new Set();
52867
- constructor(workspace) {
52868
- this.#state = initialHudState(workspace);
53687
+ constructor(workspace, initialNotice) {
53688
+ this.#state = {
53689
+ ...initialHudState(workspace),
53690
+ ...initialNotice ? { notice: initialNotice } : {}
53691
+ };
52869
53692
  }
52870
53693
  getSnapshot = () => this.#state;
52871
53694
  subscribe = (listener) => {
@@ -52895,10 +53718,22 @@ function connectionLabel(state) {
52895
53718
  if (state.connection === "error") return "Error";
52896
53719
  return "Disconnected";
52897
53720
  }
52898
- function activityAge(updatedAt, working, now) {
52899
- if (working) return "now";
52900
- if (updatedAt === void 0) return "";
52901
- const elapsedMs = Math.max(0, now - updatedAt);
53721
+ function liveDuration(elapsedMs) {
53722
+ const seconds = Math.floor(Math.max(0, elapsedMs) / 1e3);
53723
+ if (seconds < 1) return "now";
53724
+ if (seconds < 60) return `${seconds}s`;
53725
+ const minutes = Math.floor(seconds / 60);
53726
+ if (minutes < 60) return `${minutes}m ${String(seconds % 60).padStart(2, "0")}s`;
53727
+ const hours = Math.floor(minutes / 60);
53728
+ return `${hours}h ${String(minutes % 60).padStart(2, "0")}m`;
53729
+ }
53730
+ function activityAge(activity, now) {
53731
+ if (activity.state === "working") {
53732
+ const startedAt = activity.startedAt ?? activity.updatedAt;
53733
+ return startedAt === void 0 ? "now" : liveDuration(now - startedAt);
53734
+ }
53735
+ if (activity.updatedAt === void 0) return "";
53736
+ const elapsedMs = Math.max(0, now - activity.updatedAt);
52902
53737
  if (elapsedMs < 1e4) return "just now";
52903
53738
  if (elapsedMs < 6e4) return `${Math.floor(elapsedMs / 1e3)}s ago`;
52904
53739
  if (elapsedMs < 36e5) return `${Math.floor(elapsedMs / 6e4)}m ago`;
@@ -52913,6 +53748,14 @@ function activityStatus(activity) {
52913
53748
  function activitySummary(activity) {
52914
53749
  return [activity.summary.target, ...activity.summary.details].join(" \xB7 ");
52915
53750
  }
53751
+ function selectedActivity(state) {
53752
+ if (!state.activitySelection) return void 0;
53753
+ return state.activities.find((activity) => activity.requestId === state.activitySelection);
53754
+ }
53755
+ function selectedActivityIndex(state) {
53756
+ const selected = selectedActivity(state);
53757
+ return selected ? state.activities.findIndex((activity) => activity.requestId === selected.requestId) : -1;
53758
+ }
52916
53759
  function sectionLabel(value) {
52917
53760
  return value.toUpperCase();
52918
53761
  }
@@ -52926,42 +53769,68 @@ function adjacentAccessProfile(accessProfile, direction) {
52926
53769
  }
52927
53770
  function accessProfileCapabilities(accessProfile) {
52928
53771
  if (accessProfile === "read-only") {
52929
- return { summary: "Read files only", detail: "No changes \xB7 no commands" };
53772
+ return { summary: "read files", detail: "No writes \xB7 no commands" };
52930
53773
  }
52931
53774
  if (accessProfile === "workspace") {
52932
- return { summary: "Read + write files", detail: "Commands disabled" };
53775
+ return { summary: "read + write files", detail: "Commands off" };
52933
53776
  }
52934
53777
  return {
52935
- summary: "Read + write files + commands",
52936
- detail: "OS account permissions apply"
53778
+ summary: "read + write files + commands",
53779
+ detail: "OS permissions \xB7 credentials \xB7 network"
52937
53780
  };
52938
53781
  }
52939
- function primaryFooterHints() {
53782
+ function primaryFooterHints(state) {
53783
+ if (state.prompt) return [];
53784
+ if (state.busy) return [{ key: "Q", label: "Quit", tone: COLORS.coral }];
52940
53785
  return [
52941
53786
  { key: "A", label: "Activity" },
52942
53787
  { key: "W", label: "Workspace" },
52943
53788
  { key: "D", label: "Devices" },
52944
- { key: "?", label: "Help" },
52945
53789
  { key: "Q", label: "Quit", tone: COLORS.coral }
52946
53790
  ];
52947
53791
  }
52948
- function contextualFooterHints(state) {
53792
+ function contextualFooterHints(state, usable, bodyBudget) {
53793
+ if (state.prompt || state.busy) return [];
52949
53794
  if (state.view === "activity") {
53795
+ if (state.activities.length === 0) return [];
53796
+ const density = {
53797
+ key: "Tab",
53798
+ label: state.activityMode === "compact" ? "Detailed" : "Compact",
53799
+ labelWidth: "Detailed".length
53800
+ };
53801
+ if (state.activitySelection) {
53802
+ return [
53803
+ density,
53804
+ { key: "\u2191\u2193", label: "Select" },
53805
+ { key: "Enter", label: "Inspect" },
53806
+ { key: "Esc", label: "Browse" }
53807
+ ];
53808
+ }
53809
+ const window2 = activityBrowseWindow(state, bodyBudget);
53810
+ return [
53811
+ density,
53812
+ ...window2.start > 0 ? [{ key: "\u2191", label: "Older" }] : [],
53813
+ ...window2.end < state.activities.length ? [{ key: "\u2193", label: "Newer" }] : [],
53814
+ ...window2.end > window2.start ? [{ key: "Enter", label: "Select" }] : []
53815
+ ];
53816
+ }
53817
+ if (state.view === "activity-detail") {
53818
+ const maxScroll = activityDetailMaxScroll(state, usable, bodyBudget, Date.now());
53819
+ const hiddenBelow = Math.max(0, maxScroll - state.activityDetailScroll);
52950
53820
  return [
52951
- { key: "\u2191", label: "Older" },
52952
- { key: "\u2193", label: "Newer" }
53821
+ ...hiddenBelow > 0 ? [{ key: "\u2193", label: `${hiddenBelow} more` }] : [],
53822
+ { key: "Esc", label: "Back" }
52953
53823
  ];
52954
53824
  }
52955
- if (state.view === "devices" && (state.status?.devices.length ?? 0) > 0) {
53825
+ if (state.view === "devices" && (state.status?.devices.length ?? 0) > 0 && statusDeviceCapacity(state, bodyBudget, usable) > 0) {
52956
53826
  return [
52957
53827
  { key: "\u2191\u2193", label: "Select" },
52958
- { key: "Enter/R", label: "Revoke", tone: COLORS.coral }
53828
+ { key: "Enter", label: "Revoke", tone: COLORS.coral }
52959
53829
  ];
52960
53830
  }
52961
- if (state.view === "workspace" && state.accessProfile) {
52962
- const displayedAccessProfile = state.pendingAccessProfile ?? state.accessProfile;
52963
- const lower = adjacentAccessProfile(displayedAccessProfile, -1);
52964
- const higher = adjacentAccessProfile(displayedAccessProfile, 1);
53831
+ if (state.view === "workspace" && state.connection === "connected" && state.accessProfile && !state.pendingAccessProfile) {
53832
+ const lower = adjacentAccessProfile(state.accessProfile, -1);
53833
+ const higher = adjacentAccessProfile(state.accessProfile, 1);
52965
53834
  return [
52966
53835
  ...lower ? [{ key: "\u2190", label: accessProfileLabel(lower) }] : [],
52967
53836
  ...higher ? [{ key: "\u2192", label: accessProfileLabel(higher), tone: COLORS.coral }] : []
@@ -52970,7 +53839,7 @@ function contextualFooterHints(state) {
52970
53839
  return [];
52971
53840
  }
52972
53841
  function footerHintWidth(hint) {
52973
- return hint.key.length + hint.label.length + 1;
53842
+ return hint.key.length + Math.max(hint.label.length, hint.labelWidth ?? 0) + 1;
52974
53843
  }
52975
53844
  function footerHintsWidth(hints) {
52976
53845
  if (hints.length === 0) return 0;
@@ -52991,13 +53860,19 @@ function splitFooterHints(hints, usable) {
52991
53860
  }
52992
53861
  return rows;
52993
53862
  }
52994
- function buildFooterRows(state, usable) {
52995
- const rows = splitFooterHints(primaryFooterHints(), usable).map((left) => ({
53863
+ function buildFooterRows(state, usable, bodyBudget) {
53864
+ const rows = splitFooterHints(primaryFooterHints(state), usable).map((left) => ({
52996
53865
  left,
52997
53866
  right: []
52998
53867
  }));
52999
- const contextualRows = splitFooterHints(contextualFooterHints(state), usable);
53868
+ const contextualRows = splitFooterHints(
53869
+ contextualFooterHints(state, usable, bodyBudget),
53870
+ usable
53871
+ );
53000
53872
  if (contextualRows.length === 0) return rows;
53873
+ if (rows.length === 0) {
53874
+ return contextualRows.map((right) => ({ left: [], right }));
53875
+ }
53001
53876
  const firstContextual = contextualRows[0];
53002
53877
  const lastPrimary = rows.at(-1);
53003
53878
  const combinedWidth = footerHintsWidth(lastPrimary.left) + 3 + footerHintsWidth(firstContextual);
@@ -53009,57 +53884,177 @@ function buildFooterRows(state, usable) {
53009
53884
  }
53010
53885
  return rows;
53011
53886
  }
53012
- function promptText(state) {
53013
- if (state.busy) return { message: "Working\u2026" };
53014
- if (!state.prompt) return void 0;
53015
- if (state.prompt.type === "access-confirm") {
53016
- const detail = state.prompt.accessProfile === "system" ? "Commands will inherit this OS account's permissions." : "This allows guarded file writes.";
53887
+ function wrapOverlayText(value, width) {
53888
+ if (width <= 0) return [""];
53889
+ const words = value.split(/\s+/).filter(Boolean);
53890
+ if (words.length === 0) return [""];
53891
+ const lines = [];
53892
+ let line = "";
53893
+ for (const word of words) {
53894
+ if (!line) {
53895
+ line = word;
53896
+ continue;
53897
+ }
53898
+ if (`${line} ${word}`.length <= width) {
53899
+ line += ` ${word}`;
53900
+ continue;
53901
+ }
53902
+ lines.push(line);
53903
+ line = word;
53904
+ }
53905
+ if (line) lines.push(line);
53906
+ return lines.flatMap((part) => {
53907
+ if (part.length <= width) return [part];
53908
+ const chunks = [];
53909
+ for (let index = 0; index < part.length; index += width) {
53910
+ chunks.push(part.slice(index, index + width));
53911
+ }
53912
+ return chunks;
53913
+ });
53914
+ }
53915
+ function overlayContent(state, usable) {
53916
+ if (state.busy) {
53017
53917
  return {
53018
- message: `Increase access to ${accessProfileLabel(state.prompt.accessProfile)}? ${detail}`,
53019
- choices: "Y confirm N cancel"
53918
+ lines: wrapOverlayText(state.busyMessage ?? "Working\u2026", usable),
53919
+ tone: COLORS.purpleReadable,
53920
+ bold: true
53020
53921
  };
53021
53922
  }
53022
- const device = state.status?.devices[state.prompt.deviceIndex];
53923
+ if (state.prompt?.type === "access-confirm") {
53924
+ const warning = state.prompt.accessProfile === "system" ? "Commands use this OS account's permissions, environment, credentials, and network." : "Allows guarded file writes inside this workspace.";
53925
+ return {
53926
+ lines: [
53927
+ ...wrapOverlayText(`Increase access to ${accessProfileLabel(state.prompt.accessProfile)}?`, usable),
53928
+ ...wrapOverlayText(warning, usable)
53929
+ ],
53930
+ choices: "Y Confirm N Cancel",
53931
+ tone: COLORS.coral,
53932
+ bold: true
53933
+ };
53934
+ }
53935
+ if (state.prompt?.type === "revoke-confirm") {
53936
+ const device = state.status?.devices[state.prompt.deviceIndex];
53937
+ return {
53938
+ lines: wrapOverlayText(`Revoke ${device?.name ?? "this device"}?`, usable),
53939
+ choices: "Y Confirm N Cancel",
53940
+ tone: COLORS.coral,
53941
+ bold: true
53942
+ };
53943
+ }
53944
+ if (!state.notice) return void 0;
53945
+ const tone = state.noticeTone === "error" ? COLORS.coral : state.noticeTone === "success" ? COLORS.success : COLORS.purpleReadable;
53023
53946
  return {
53024
- message: `Revoke ${device?.name ?? "this device"}?`,
53025
- choices: "Y confirm N cancel"
53947
+ lines: wrapOverlayText(state.notice, usable),
53948
+ tone,
53949
+ bold: false
53026
53950
  };
53027
53951
  }
53028
53952
  function screenMetrics(state, columns, rows) {
53029
53953
  const margin = columns >= 24 ? 2 : 0;
53030
53954
  const usable = Math.max(8, columns - margin * 2);
53031
53955
  const terminalRows = Math.max(6, rows);
53032
- const footerRows = buildFooterRows(state, usable);
53033
- const prompt = promptText(state);
53034
- const overlayRows = prompt || state.notice ? 2 + (prompt?.choices ? 1 : 0) : 0;
53035
- const bodyBudget = Math.max(
53036
- 0,
53037
- terminalRows - 2 - overlayRows - 1 - footerRows.length
53038
- );
53956
+ const overlay = overlayContent(state, usable);
53957
+ const overlayRows = overlay ? 1 + overlay.lines.length + (overlay.choices ? 1 : 0) : 0;
53958
+ let bodyBudget = Math.max(0, terminalRows - 2 - overlayRows - 1);
53959
+ let footerRows = [];
53960
+ for (let iteration = 0; iteration < 4; iteration += 1) {
53961
+ const nextFooterRows = buildFooterRows(state, usable, bodyBudget);
53962
+ const nextBodyBudget = Math.max(
53963
+ 0,
53964
+ terminalRows - 2 - overlayRows - 1 - nextFooterRows.length
53965
+ );
53966
+ footerRows = nextFooterRows;
53967
+ if (nextBodyBudget === bodyBudget) break;
53968
+ bodyBudget = nextBodyBudget;
53969
+ }
53039
53970
  return { usable, bodyBudget, footerRows, overlayRows };
53040
53971
  }
53041
- function activityPageCapacity(bodyBudget) {
53972
+ function activityListCapacity(bodyBudget) {
53042
53973
  return Math.max(0, bodyBudget - ACTIVITY_PREAMBLE_LINES);
53043
53974
  }
53044
- function activityMaxPage(activityCount, pageCapacity) {
53045
- if (pageCapacity <= 0 || activityCount <= pageCapacity) return 0;
53046
- return Math.floor((activityCount - 1) / pageCapacity);
53047
- }
53048
- function activityPageInfo(state, bodyBudget) {
53049
- const capacity = activityPageCapacity(bodyBudget);
53050
- const maxPage = activityMaxPage(state.activities.length, capacity);
53051
- const page = Math.min(state.activityPage, maxPage);
53975
+ function activityBrowseWindow(state, bodyBudget) {
53976
+ const capacity = activityListCapacity(bodyBudget);
53977
+ const total = state.activities.length;
53978
+ if (capacity <= 0 || total === 0) return { capacity, start: 0, end: 0 };
53979
+ let end = total;
53980
+ if (state.activityBrowseAnchor) {
53981
+ const anchorIndex = state.activities.findIndex(
53982
+ (activity) => activity.requestId === state.activityBrowseAnchor
53983
+ );
53984
+ end = anchorIndex >= 0 ? anchorIndex + 1 : Math.min(total, capacity);
53985
+ }
53052
53986
  return {
53053
53987
  capacity,
53054
- maxPage,
53055
- page,
53056
- rangeStart: page * capacity + 1,
53057
- rangeEnd: Math.min((page + 1) * capacity, state.activities.length)
53988
+ start: Math.max(0, end - capacity),
53989
+ end
53990
+ };
53991
+ }
53992
+ function activityWindow(state, bodyBudget) {
53993
+ const browsing = activityBrowseWindow(state, bodyBudget);
53994
+ const selection = selectedActivityIndex(state);
53995
+ if (selection < 0 || browsing.capacity <= 0 || selection >= browsing.start && selection < browsing.end) {
53996
+ return { ...browsing, selection };
53997
+ }
53998
+ const total = state.activities.length;
53999
+ const maxStart = Math.max(0, total - browsing.capacity);
54000
+ const centered = selection - Math.floor(browsing.capacity / 2);
54001
+ const start = Math.min(maxStart, Math.max(0, centered));
54002
+ return {
54003
+ capacity: browsing.capacity,
54004
+ selection,
54005
+ start,
54006
+ end: Math.min(total, start + browsing.capacity)
54007
+ };
54008
+ }
54009
+ function activityBrowseAnchorForEnd(activities, end) {
54010
+ if (end >= activities.length) return void 0;
54011
+ return end > 0 ? activities[end - 1]?.requestId : void 0;
54012
+ }
54013
+ function activityCenterSelection(state, bodyBudget) {
54014
+ const { start, end } = activityBrowseWindow(state, bodyBudget);
54015
+ if (end <= start) return void 0;
54016
+ const index = start + Math.floor((end - start) / 2);
54017
+ return state.activities[index]?.requestId;
54018
+ }
54019
+ function activityPageUpdate(state, bodyBudget, direction) {
54020
+ const window2 = activityBrowseWindow(state, bodyBudget);
54021
+ if (window2.capacity <= 0) return void 0;
54022
+ if (direction === "older") {
54023
+ if (window2.start <= 0) return void 0;
54024
+ return {
54025
+ activityBrowseAnchor: activityBrowseAnchorForEnd(state.activities, window2.start)
54026
+ };
54027
+ }
54028
+ if (window2.end >= state.activities.length) return void 0;
54029
+ const end = Math.min(state.activities.length, window2.end + window2.capacity);
54030
+ return {
54031
+ activityBrowseAnchor: activityBrowseAnchorForEnd(state.activities, end)
54032
+ };
54033
+ }
54034
+ function activitySelectionUpdate(state, bodyBudget, direction) {
54035
+ const currentIndex = selectedActivityIndex(state);
54036
+ if (currentIndex < 0) return void 0;
54037
+ const targetIndex = Math.min(
54038
+ state.activities.length - 1,
54039
+ Math.max(0, currentIndex + direction)
54040
+ );
54041
+ if (targetIndex === currentIndex) return void 0;
54042
+ const window2 = activityWindow(state, bodyBudget);
54043
+ let activityBrowseAnchor = state.activityBrowseAnchor;
54044
+ if (targetIndex < window2.start) {
54045
+ activityBrowseAnchor = activityBrowseAnchorForEnd(state.activities, window2.start);
54046
+ } else if (targetIndex >= window2.end) {
54047
+ const end = Math.min(state.activities.length, window2.end + window2.capacity);
54048
+ activityBrowseAnchor = activityBrowseAnchorForEnd(state.activities, end);
54049
+ }
54050
+ return {
54051
+ activitySelection: state.activities[targetIndex].requestId,
54052
+ activityBrowseAnchor
53058
54053
  };
53059
54054
  }
53060
54055
  function statusDeviceCapacity(state, bodyBudget, usable) {
53061
54056
  if (!state.status || state.statusLoading || state.status.devices.length === 0) return 0;
53062
- const preamble = usable >= 64 ? 10 : 9;
54057
+ const preamble = usable >= 64 ? 2 : 1;
53063
54058
  return Math.min(9, state.status.devices.length, Math.max(0, bodyBudget - preamble));
53064
54059
  }
53065
54060
  function deviceListWindow(state, bodyBudget, usable) {
@@ -53081,9 +54076,10 @@ function Line({ usable, color }) {
53081
54076
  }
53082
54077
  function Header({ state, usable, bodyBudget, color }) {
53083
54078
  const statusColor = state.connection === "error" ? COLORS.coral : COLORS.purpleReadable;
53084
- const pageTitle = state.view === "activity" ? "Activity" : state.view === "devices" ? "Devices" : state.view === "workspace" ? "Workspace" : state.view === "help" ? "Help" : void 0;
53085
- const activityPage = state.view === "activity" ? activityPageInfo(state, bodyBudget) : void 0;
53086
- const activityRange = activityPage && activityPage.maxPage > 0 ? ` (${activityPage.rangeStart}-${activityPage.rangeEnd}/${state.activities.length})` : "";
54079
+ const selected = selectedActivity(state);
54080
+ const pageTitle = state.view === "activity" ? "Activity" : state.view === "activity-detail" ? `Activity / ${selected ? activityToolTitle(selected.tool) : "Call"}` : state.view === "devices" ? "Devices" : "Workspace";
54081
+ const activityListWindow = state.view === "activity" ? activityWindow(state, bodyBudget) : void 0;
54082
+ const activityRange = activityListWindow && activityListWindow.capacity > 0 && state.activities.length > activityListWindow.capacity ? ` (${activityListWindow.start + 1}-${activityListWindow.end}/${state.activities.length})` : "";
53087
54083
  const deviceWindow = state.view === "devices" ? deviceListWindow(state, bodyBudget, usable) : void 0;
53088
54084
  const deviceRange = deviceWindow && state.status && deviceWindow.capacity > 0 && state.status.devices.length > deviceWindow.capacity ? ` (${deviceWindow.start + 1}-${deviceWindow.end}/${state.status.devices.length})` : "";
53089
54085
  return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Box_default, { flexDirection: "column", height: 2, flexShrink: 0, children: [
@@ -53107,76 +54103,105 @@ function SectionTitle({ children, color, tone = COLORS.purpleReadable }) {
53107
54103
  function Blank() {
53108
54104
  return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Box_default, { height: 1 });
53109
54105
  }
53110
- function ActivityPreviewHeader({ usable, color }) {
53111
- return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Box_default, { width: usable, children: [
53112
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Box_default, { flexGrow: 1, flexShrink: 1, children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(SectionTitle, { color, children: "Activity" }) }),
53113
- usable >= 24 ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Box_default, { marginLeft: 1, flexShrink: 0, children: [
53114
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Text2, { bold: true, color: color ? COLORS.purpleReadable : void 0, children: "A" }),
53115
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Text2, { color: color ? COLORS.muted : void 0, children: " View all" })
54106
+ function WorkspaceFact({ label, value, usable, color, tone, hint }) {
54107
+ const labelWidth = Math.min(10, Math.max(7, Math.floor(usable * 0.16)));
54108
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Box_default, { width: usable, height: 1, flexShrink: 0, children: [
54109
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Box_default, { width: labelWidth, flexShrink: 0, children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Text2, { color: color ? COLORS.muted : void 0, children: label }) }),
54110
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Box_default, { flexGrow: 1, flexShrink: 1, children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Text2, { color: color ? tone ?? COLORS.ink : void 0, wrap: "truncate", children: value }) }),
54111
+ hint && usable >= 40 ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Box_default, { marginLeft: 2, flexShrink: 0, children: [
54112
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Text2, { bold: true, color: color ? hint.tone ?? COLORS.purpleReadable : void 0, children: hint.key }),
54113
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Text2, { color: color ? COLORS.muted : void 0, children: ` ${hint.label}` })
53116
54114
  ] }) : null
53117
54115
  ] });
53118
54116
  }
53119
- function AccessSectionTitle({ accessProfile, usable, color }) {
53120
- const lower = adjacentAccessProfile(accessProfile, -1);
53121
- const higher = adjacentAccessProfile(accessProfile, 1);
53122
- const key = lower && higher ? "\u2190/\u2192" : lower ? "\u2190" : "\u2192";
54117
+ function WorkspaceActivityHeader({ usable, color }) {
53123
54118
  return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Box_default, { width: usable, children: [
53124
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(SectionTitle, { color, children: "Access" }),
53125
- usable >= 24 ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Box_default, { marginLeft: 3, flexShrink: 0, children: [
53126
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Text2, { bold: true, color: color ? COLORS.purpleReadable : void 0, children: key }),
53127
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Text2, { color: color ? COLORS.muted : void 0, children: " Switch" })
54119
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Box_default, { flexGrow: 1, flexShrink: 1, children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(SectionTitle, { color, children: "Activity" }) }),
54120
+ usable >= 32 ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Box_default, { marginLeft: 2, flexShrink: 0, children: [
54121
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Text2, { bold: true, color: color ? COLORS.purpleReadable : void 0, children: "A" }),
54122
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Text2, { color: color ? COLORS.muted : void 0, children: " View all" })
53128
54123
  ] }) : null
53129
54124
  ] });
53130
54125
  }
53131
- function activityLayout(usable) {
54126
+ function activityLayout(usable, selectable) {
54127
+ const selectorWidth = selectable ? ACTIVITY_SELECTOR_COLUMN_WIDTH : 0;
54128
+ const leadingWidth = selectorWidth + ACTIVITY_STATUS_COLUMN_WIDTH;
53132
54129
  const toolWidth = Math.min(
53133
54130
  ACTIVITY_TOOL_COLUMN_WIDTH,
53134
- Math.max(0, usable - ACTIVITY_STATUS_COLUMN_WIDTH)
54131
+ Math.max(0, usable - leadingWidth)
53135
54132
  );
53136
- return {
53137
- toolWidth,
53138
- showSummary: usable >= ACTIVITY_STATUS_COLUMN_WIDTH + toolWidth + 5,
53139
- showAge: usable >= ACTIVITY_STATUS_COLUMN_WIDTH + toolWidth + ACTIVITY_AGE_COLUMN_WIDTH + 10
53140
- };
54133
+ const showSummary = usable >= leadingWidth + toolWidth + 5;
54134
+ const showAge = usable >= leadingWidth + toolWidth + ACTIVITY_AGE_COLUMN_WIDTH + 10;
54135
+ const summaryWidth = Math.max(
54136
+ 0,
54137
+ usable - leadingWidth - toolWidth - (showSummary ? 2 : 0) - (showAge ? ACTIVITY_AGE_COLUMN_WIDTH + 2 : 0)
54138
+ );
54139
+ return { toolWidth, showSummary, showAge, summaryWidth };
53141
54140
  }
53142
54141
  function WorkspaceView({ state, usable, bodyBudget, color, now }) {
53143
- const displayedAccessProfile = state.pendingAccessProfile ?? state.accessProfile;
53144
54142
  const showConnectionMessage = Boolean(
53145
54143
  state.message && (state.connection === "retrying" || state.connection === "error")
53146
54144
  );
53147
- let fixedLines = 3;
53148
- if (displayedAccessProfile) fixedLines += 5;
53149
- if (state.deviceName) fixedLines += 3;
54145
+ const hasFacts = Boolean(state.deviceName || state.accessProfile);
54146
+ let fixedLines = 2;
54147
+ if (hasFacts) fixedLines += 1;
54148
+ if (state.deviceName) fixedLines += 1;
54149
+ if (state.accessProfile) fixedLines += 2;
53150
54150
  if (showConnectionMessage) fixedLines += 2;
53151
- const activityCapacity = Math.min(3, Math.max(0, bodyBudget - fixedLines - 2));
54151
+ const activityCapacity = !state.prompt && !state.busy && state.activities.length > 0 ? Math.min(3, Math.max(0, bodyBudget - fixedLines - 3)) : 0;
53152
54152
  const activityPreview = activityCapacity > 0 ? state.activities.slice(-activityCapacity) : [];
54153
+ const accessHint = state.connection === "connected" && state.accessProfile && !state.pendingAccessProfile && !state.prompt && !state.busy ? (() => {
54154
+ const lower = adjacentAccessProfile(state.accessProfile, -1);
54155
+ const higher = adjacentAccessProfile(state.accessProfile, 1);
54156
+ return {
54157
+ key: lower && higher ? "\u2190/\u2192" : lower ? "\u2190" : "\u2192",
54158
+ label: "Switch"
54159
+ };
54160
+ })() : void 0;
53153
54161
  return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Box_default, { height: bodyBudget, flexDirection: "column", flexShrink: 0, overflow: "hidden", children: [
53154
54162
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Blank, {}),
53155
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(SectionTitle, { color, children: "Workspace" }),
53156
54163
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Text2, { color: color ? COLORS.ink : void 0, wrap: "truncate-middle", children: state.workspace }),
53157
- displayedAccessProfile ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [
53158
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Blank, {}),
54164
+ hasFacts ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Blank, {}) : null,
54165
+ state.deviceName ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
54166
+ WorkspaceFact,
54167
+ {
54168
+ label: "Device",
54169
+ value: state.deviceName,
54170
+ usable,
54171
+ color
54172
+ }
54173
+ ) : null,
54174
+ state.accessProfile ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [
53159
54175
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
53160
- AccessSectionTitle,
54176
+ WorkspaceFact,
53161
54177
  {
53162
- accessProfile: displayedAccessProfile,
54178
+ label: "Access",
54179
+ value: `${accessProfileLabel(state.accessProfile)} \xB7 ${accessProfileCapabilities(state.accessProfile).summary}`,
53163
54180
  usable,
53164
- color
54181
+ color,
54182
+ hint: accessHint
53165
54183
  }
53166
54184
  ),
53167
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Text2, { bold: true, color: color ? COLORS.ink : void 0, wrap: "truncate", children: accessProfileLabel(displayedAccessProfile) }),
53168
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Text2, { color: color ? COLORS.muted : void 0, wrap: "truncate", children: accessProfileCapabilities(displayedAccessProfile).summary }),
53169
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Text2, { color: color ? COLORS.muted : void 0, wrap: "truncate", children: accessProfileCapabilities(displayedAccessProfile).detail })
54185
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
54186
+ WorkspaceFact,
54187
+ {
54188
+ label: "",
54189
+ value: accessProfileCapabilities(state.accessProfile).detail,
54190
+ usable,
54191
+ color
54192
+ }
54193
+ )
53170
54194
  ] }) : null,
53171
- state.deviceName ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [
54195
+ showConnectionMessage ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [
53172
54196
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Blank, {}),
53173
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(SectionTitle, { color, children: "Device" }),
53174
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Text2, { color: color ? COLORS.ink : void 0, wrap: "truncate", children: state.deviceName })
54197
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Text2, { color: color ? COLORS.coral : void 0, wrap: "wrap", children: state.message })
53175
54198
  ] }) : null,
53176
54199
  activityCapacity > 0 ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [
53177
54200
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Blank, {}),
53178
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(ActivityPreviewHeader, { usable, color }),
53179
- 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)(
54201
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Line, { usable, color }),
54202
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Blank, {}),
54203
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(WorkspaceActivityHeader, { usable, color }),
54204
+ activityPreview.map((activity) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
53180
54205
  ActivityRow,
53181
54206
  {
53182
54207
  activity,
@@ -53186,29 +54211,35 @@ function WorkspaceView({ state, usable, bodyBudget, color, now }) {
53186
54211
  },
53187
54212
  activity.requestId
53188
54213
  ))
53189
- ] }) : null,
53190
- showConnectionMessage ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [
53191
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Blank, {}),
53192
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Text2, { color: color ? COLORS.coral : void 0, wrap: "truncate", children: state.message })
53193
54214
  ] }) : null
53194
54215
  ] });
53195
54216
  }
53196
- function ActivityRow({ activity, usable, color, now }) {
53197
- const { toolWidth, showSummary, showAge } = activityLayout(usable);
53198
- const age = activityAge(activity.updatedAt, activity.state === "working", now);
54217
+ function ActivityRow({
54218
+ activity,
54219
+ usable,
54220
+ color,
54221
+ now,
54222
+ selectable = false,
54223
+ selected = false,
54224
+ mode = "compact"
54225
+ }) {
54226
+ const { toolWidth, showSummary, showAge, summaryWidth } = activityLayout(usable, selectable);
54227
+ const age = activityAge(activity, now);
53199
54228
  const status = activityStatus(activity);
54229
+ const summary = activity.call ? formatActivityCall(activity.call, mode, summaryWidth) : mode === "compact" ? activity.compactSummary ?? activitySummary(activity) : activitySummary(activity);
53200
54230
  return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Box_default, { width: usable, height: 1, flexShrink: 0, children: [
54231
+ selectable ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Box_default, { width: ACTIVITY_SELECTOR_COLUMN_WIDTH, flexShrink: 0, children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Text2, { bold: true, color: color ? COLORS.purpleReadable : void 0, children: selected ? "\u203A" : " " }) }) : null,
53201
54232
  /* @__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 }) }),
53202
54233
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Box_default, { width: toolWidth, flexShrink: 0, children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
53203
54234
  Text2,
53204
54235
  {
53205
54236
  bold: true,
53206
- color: color ? COLORS.ink : void 0,
54237
+ color: color ? selected ? COLORS.purpleReadable : COLORS.ink : void 0,
53207
54238
  wrap: "truncate",
53208
54239
  children: activity.tool
53209
54240
  }
53210
54241
  ) }),
53211
- 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,
54242
+ 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", children: summary }) }) : null,
53212
54243
  showAge ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
53213
54244
  Box_default,
53214
54245
  {
@@ -53222,10 +54253,9 @@ function ActivityRow({ activity, usable, color, now }) {
53222
54253
  ] });
53223
54254
  }
53224
54255
  function ActivityView({ state, usable, bodyBudget, color, now }) {
53225
- const { capacity, page } = activityPageInfo(state, bodyBudget);
53226
- const pageEnd = state.activities.length - page * capacity;
53227
- const pageStart = Math.max(0, pageEnd - capacity);
53228
- const visible = capacity > 0 ? state.activities.slice(pageStart, pageEnd) : [];
54256
+ const window2 = activityWindow(state, bodyBudget);
54257
+ const visible = window2.capacity > 0 ? state.activities.slice(window2.start, window2.end) : [];
54258
+ const selection = selectedActivity(state)?.requestId;
53229
54259
  return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Box_default, { height: bodyBudget, flexDirection: "column", flexShrink: 0, overflow: "hidden", children: [
53230
54260
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Blank, {}),
53231
54261
  state.activities.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Text2, { color: color ? COLORS.muted : void 0, children: "No activity yet." }) : visible.map((activity) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
@@ -53234,25 +54264,132 @@ function ActivityView({ state, usable, bodyBudget, color, now }) {
53234
54264
  activity,
53235
54265
  usable,
53236
54266
  color,
53237
- now
54267
+ now,
54268
+ selectable: true,
54269
+ selected: activity.requestId === selection,
54270
+ mode: state.activityMode
53238
54271
  },
53239
54272
  activity.requestId
53240
54273
  ))
53241
54274
  ] });
53242
54275
  }
53243
- function Metric({ value, label, color }) {
53244
- return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Box_default, { children: [
53245
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Text2, { color: color ? COLORS.ink : void 0, children: value }),
53246
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Text2, { color: color ? COLORS.muted : void 0, children: [
53247
- " ",
53248
- label
53249
- ] })
54276
+ function wrapDetailValue(value, width) {
54277
+ if (width <= 0) return [""];
54278
+ const characters = Array.from(value);
54279
+ if (characters.length <= width) return [value];
54280
+ const lines = [];
54281
+ let remaining = characters;
54282
+ while (remaining.length > width) {
54283
+ let cut = width;
54284
+ for (let index = width - 1; index >= Math.floor(width * 0.55); index -= 1) {
54285
+ if (remaining[index] === " ") {
54286
+ cut = index;
54287
+ break;
54288
+ }
54289
+ }
54290
+ lines.push(remaining.slice(0, cut).join(""));
54291
+ remaining = remaining.slice(cut);
54292
+ while (remaining[0] === " ") remaining = remaining.slice(1);
54293
+ }
54294
+ if (remaining.length > 0) lines.push(remaining.join(""));
54295
+ return lines;
54296
+ }
54297
+ function detailFieldLines(label, value, usable) {
54298
+ const labelWidth = Math.min(14, Math.max(8, Math.floor(usable * 0.3)));
54299
+ const valueWidth = Math.max(1, usable - labelWidth);
54300
+ return wrapDetailValue(value, valueWidth).map((part, index) => ({
54301
+ label: index === 0 ? label : "",
54302
+ value: part
54303
+ }));
54304
+ }
54305
+ function activityInspectOutputLines(activity, usable) {
54306
+ const preview = activity.output?.preview;
54307
+ if (!preview) return [];
54308
+ const tone = activity.output?.kind === "error" ? COLORS.coral : void 0;
54309
+ const lines = [];
54310
+ for (const part of preview.split("\n")) {
54311
+ for (const wrapped of wrapDetailValue(part || " ", usable)) {
54312
+ lines.push({ value: wrapped, fullWidth: true, ...tone ? { tone } : {} });
54313
+ }
54314
+ }
54315
+ return lines;
54316
+ }
54317
+ function formatClock(timestamp) {
54318
+ const date5 = new Date(timestamp);
54319
+ return `${String(date5.getHours()).padStart(2, "0")}:${String(date5.getMinutes()).padStart(2, "0")}:${String(date5.getSeconds()).padStart(2, "0")}`;
54320
+ }
54321
+ function activityInspectLines(activity, usable, now) {
54322
+ const lines = [{}];
54323
+ if (activity.call) {
54324
+ for (const field of activityCallDetailFields(activity.call)) {
54325
+ lines.push(...detailFieldLines(field.label, field.value, usable));
54326
+ }
54327
+ } else {
54328
+ const reason = activity.callUnavailable === "oversized" ? "Full invocation metadata exceeded the local Activity detail budget and was not retained." : "Full invocation metadata has expired from the local Activity detail budget; the compact history remains available.";
54329
+ lines.push(...detailFieldLines("details", reason, usable));
54330
+ }
54331
+ if (activity.startedAt !== void 0) {
54332
+ lines.push(...detailFieldLines("started", formatClock(activity.startedAt), usable));
54333
+ const end = activity.state === "working" ? now : activity.updatedAt ?? now;
54334
+ lines.push(...detailFieldLines("duration", liveDuration(end - activity.startedAt), usable));
54335
+ }
54336
+ lines.push({}, { separator: true }, {}, { section: "Output" });
54337
+ lines.push(...activityInspectOutputLines(activity, usable));
54338
+ return lines;
54339
+ }
54340
+ function ActivityDetailLine({ line, usable, color }) {
54341
+ if (line.section) return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(SectionTitle, { color, children: line.section });
54342
+ if (line.separator) return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Line, { usable, color });
54343
+ if (line.fullWidth) {
54344
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
54345
+ Text2,
54346
+ {
54347
+ bold: Boolean(line.bold),
54348
+ color: color ? line.tone ?? COLORS.ink : void 0,
54349
+ wrap: "truncate",
54350
+ children: line.value ?? ""
54351
+ }
54352
+ );
54353
+ }
54354
+ if (line.label === void 0 && line.value === void 0) return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Blank, {});
54355
+ const labelWidth = Math.min(14, Math.max(8, Math.floor(usable * 0.3)));
54356
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Box_default, { width: usable, height: 1, flexShrink: 0, children: [
54357
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Box_default, { width: labelWidth, flexShrink: 0, children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Text2, { color: color ? COLORS.muted : void 0, children: line.label ?? "" }) }),
54358
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
54359
+ Text2,
54360
+ {
54361
+ bold: Boolean(line.bold),
54362
+ color: color ? line.tone ?? COLORS.ink : void 0,
54363
+ wrap: "truncate",
54364
+ children: line.value ?? ""
54365
+ }
54366
+ )
53250
54367
  ] });
53251
54368
  }
54369
+ function ActivityDetailView({ state, usable, bodyBudget, color, now }) {
54370
+ const activity = selectedActivity(state);
54371
+ if (!activity) {
54372
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Box_default, { height: bodyBudget, flexDirection: "column", flexShrink: 0, overflow: "hidden", children: [
54373
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Blank, {}),
54374
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Text2, { color: color ? COLORS.muted : void 0, children: "No activity selected." })
54375
+ ] });
54376
+ }
54377
+ const lines = activityInspectLines(activity, usable, now);
54378
+ const maxScroll = Math.max(0, lines.length - bodyBudget);
54379
+ const scroll = Math.min(state.activityDetailScroll, maxScroll);
54380
+ const visible = lines.slice(scroll, scroll + bodyBudget);
54381
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Box_default, { height: bodyBudget, flexDirection: "column", flexShrink: 0, overflow: "hidden", children: visible.map((line, index) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)(ActivityDetailLine, { line, usable, color }, `${scroll}-${index}`)) });
54382
+ }
54383
+ function activityDetailMaxScroll(state, usable, bodyBudget, now) {
54384
+ const activity = selectedActivity(state);
54385
+ if (!activity) return 0;
54386
+ return Math.max(0, activityInspectLines(activity, usable, now).length - bodyBudget);
54387
+ }
53252
54388
  function DeviceRow({ device, selected, usable, color }) {
53253
54389
  const active = device.status.includes("active");
53254
54390
  const tone = active ? COLORS.purpleReadable : COLORS.muted;
53255
54391
  const selector = selected ? "\u203A" : " ";
54392
+ const name = device.current ? `${device.name} \xB7 this device` : device.name;
53256
54393
  if (usable < 64) {
53257
54394
  return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Box_default, { width: usable, children: [
53258
54395
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Box_default, { width: 2, flexShrink: 0, children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Text2, { bold: true, color: color ? COLORS.purpleReadable : void 0, children: selector }) }),
@@ -53262,7 +54399,7 @@ function DeviceRow({ device, selected, usable, color }) {
53262
54399
  bold: selected,
53263
54400
  color: color ? selected ? COLORS.purpleReadable : tone : void 0,
53264
54401
  wrap: "truncate",
53265
- children: `${device.name} \xB7 ${device.status} \xB7 ${device.platform} \xB7 ${device.lastSeen}`
54402
+ children: `${name} \xB7 ${device.status} \xB7 ${device.platform} \xB7 ${device.lastSeen}`
53266
54403
  }
53267
54404
  )
53268
54405
  ] });
@@ -53275,12 +54412,12 @@ function DeviceRow({ device, selected, usable, color }) {
53275
54412
  bold: selected,
53276
54413
  color: color ? selected ? COLORS.purpleReadable : COLORS.ink : void 0,
53277
54414
  wrap: "truncate",
53278
- children: device.name
54415
+ children: name
53279
54416
  }
53280
54417
  ) }),
53281
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Box_default, { width: 18, flexShrink: 0, children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Text2, { color: color ? tone : void 0, wrap: "truncate", children: device.status }) }),
54418
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Box_default, { width: 20, flexShrink: 0, children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Text2, { color: color ? tone : void 0, wrap: "truncate", children: device.status }) }),
53282
54419
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Box_default, { width: 14, flexShrink: 0, children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Text2, { color: color ? COLORS.muted : void 0, wrap: "truncate", children: device.platform }) }),
53283
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Box_default, { width: 18, flexShrink: 0, children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Text2, { color: color ? COLORS.muted : void 0, wrap: "truncate", children: device.lastSeen }) })
54420
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Box_default, { width: 14, flexShrink: 0, children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Text2, { color: color ? COLORS.muted : void 0, wrap: "truncate", children: device.lastSeen }) })
53284
54421
  ] });
53285
54422
  }
53286
54423
  function DeviceHeading({ usable, color }) {
@@ -53288,9 +54425,9 @@ function DeviceHeading({ usable, color }) {
53288
54425
  return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Box_default, { width: usable, children: [
53289
54426
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Box_default, { width: 2 }),
53290
54427
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Box_default, { flexGrow: 1, children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Text2, { color: color ? COLORS.muted : void 0, children: "Device" }) }),
53291
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Box_default, { width: 18, children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Text2, { color: color ? COLORS.muted : void 0, children: "Workers" }) }),
54428
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Box_default, { width: 20, children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Text2, { color: color ? COLORS.muted : void 0, children: "Workspaces" }) }),
53292
54429
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Box_default, { width: 14, children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Text2, { color: color ? COLORS.muted : void 0, children: "Platform" }) }),
53293
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Box_default, { width: 18, children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Text2, { color: color ? COLORS.muted : void 0, children: "Last seen" }) })
54430
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Box_default, { width: 14, children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Text2, { color: color ? COLORS.muted : void 0, children: "Last seen" }) })
53294
54431
  ] });
53295
54432
  }
53296
54433
  function DevicesView({ state, usable, bodyBudget, color }) {
@@ -53298,98 +54435,47 @@ function DevicesView({ state, usable, bodyBudget, color }) {
53298
54435
  const devices = state.status?.devices.slice(window2.start, window2.end) ?? [];
53299
54436
  return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Box_default, { height: bodyBudget, flexDirection: "column", flexShrink: 0, overflow: "hidden", children: [
53300
54437
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Blank, {}),
53301
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(SectionTitle, { color, children: "Paired" }),
53302
- state.statusLoading ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [
53303
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Blank, {}),
53304
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Text2, { color: color ? COLORS.muted : void 0, children: "Loading devices\u2026" })
53305
- ] }) : !state.status ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [
53306
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Blank, {}),
53307
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Text2, { color: color ? COLORS.muted : void 0, children: "Devices are not loaded." })
53308
- ] }) : /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [
53309
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Text2, { color: color ? COLORS.ink : void 0, wrap: "truncate", children: state.deviceName ?? "This computer" }),
53310
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Text2, { color: color ? COLORS.muted : void 0, wrap: "truncate", children: state.status.relay }),
53311
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Blank, {}),
53312
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(SectionTitle, { color, children: "Overview" }),
53313
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
53314
- Metric,
53315
- {
53316
- value: state.status.activeWorkers === null ? "Unavailable" : String(state.status.activeWorkers),
53317
- label: "Active workspaces",
53318
- color
53319
- }
53320
- ),
53321
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Metric, { value: String(state.status.devices.length), label: "Devices", color }),
53322
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Blank, {}),
53323
- state.status.devices.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Text2, { color: color ? COLORS.muted : void 0, children: "No devices." }) : /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [
53324
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(DeviceHeading, { usable, color }),
53325
- devices.map((device, index) => {
53326
- const deviceIndex = window2.start + index;
53327
- return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
53328
- DeviceRow,
53329
- {
53330
- device,
53331
- selected: deviceIndex === window2.selection,
53332
- usable,
53333
- color
53334
- },
53335
- device.id
53336
- );
53337
- })
53338
- ] })
54438
+ state.statusLoading ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Text2, { color: color ? COLORS.muted : void 0, children: "Loading\u2026" }) : !state.status ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Text2, { color: color ? COLORS.muted : void 0, children: "Devices unavailable." }) : state.status.devices.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Text2, { color: color ? COLORS.muted : void 0, children: "No paired devices." }) : /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [
54439
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(DeviceHeading, { usable, color }),
54440
+ devices.map((device, index) => {
54441
+ const deviceIndex = window2.start + index;
54442
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
54443
+ DeviceRow,
54444
+ {
54445
+ device,
54446
+ selected: deviceIndex === window2.selection,
54447
+ usable,
54448
+ color
54449
+ },
54450
+ device.id
54451
+ );
54452
+ })
53339
54453
  ] })
53340
54454
  ] });
53341
54455
  }
53342
- function HelpRow({ keyLabel, label, color, tone = COLORS.purpleReadable }) {
53343
- return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Box_default, { children: [
53344
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Box_default, { width: 8, flexShrink: 0, children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Text2, { bold: true, color: color ? tone : void 0, children: keyLabel }) }),
53345
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Text2, { color: color ? COLORS.muted : void 0, wrap: "truncate", children: label })
53346
- ] });
53347
- }
53348
- function HelpView({ bodyBudget, color }) {
53349
- return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Box_default, { height: bodyBudget, flexDirection: "column", flexShrink: 0, overflow: "hidden", children: [
53350
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Blank, {}),
53351
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(SectionTitle, { color, children: "Navigate" }),
53352
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(HelpRow, { keyLabel: "A", label: "Activity", color }),
53353
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(HelpRow, { keyLabel: "W", label: "Workspace", color }),
53354
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(HelpRow, { keyLabel: "D", label: "Devices", color }),
53355
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(HelpRow, { keyLabel: "?", label: "Help", color }),
53356
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(HelpRow, { keyLabel: "Esc", label: "Workspace", color }),
53357
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Blank, {}),
53358
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(SectionTitle, { color, tone: COLORS.coral, children: "Manage" }),
53359
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(HelpRow, { keyLabel: "\u2190/\u2192", label: "Change workspace access", color, tone: COLORS.coral }),
53360
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(HelpRow, { keyLabel: "Enter/R", label: "Revoke selected device", color, tone: COLORS.coral }),
53361
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Blank, {}),
53362
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(SectionTitle, { color, children: "App" }),
53363
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(HelpRow, { keyLabel: "Q", label: "Disconnect and quit", color, tone: COLORS.coral }),
53364
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(HelpRow, { keyLabel: "Ctrl+C", label: "Disconnect and quit", color, tone: COLORS.coral })
53365
- ] });
53366
- }
53367
54456
  function Overlay({ state, usable, color }) {
53368
- const prompt = promptText(state);
53369
- const message = prompt?.message ?? state.notice;
53370
- if (!message) return null;
54457
+ const overlay = overlayContent(state, usable);
54458
+ if (!overlay) return null;
53371
54459
  return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Box_default, { flexDirection: "column", flexShrink: 0, children: [
53372
54460
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Line, { usable, color }),
53373
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
54461
+ overlay.lines.map((line, index) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
53374
54462
  Text2,
53375
54463
  {
53376
- bold: Boolean(prompt),
53377
- color: color ? COLORS.coral : void 0,
54464
+ bold: overlay.bold,
54465
+ color: color ? overlay.tone : void 0,
53378
54466
  wrap: "truncate",
53379
- children: message
53380
- }
53381
- ),
53382
- prompt?.choices ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Text2, { color: color ? COLORS.muted : void 0, wrap: "truncate", children: prompt.choices }) : null
54467
+ children: line
54468
+ },
54469
+ index
54470
+ )),
54471
+ overlay.choices ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Text2, { color: color ? COLORS.muted : void 0, wrap: "truncate", children: overlay.choices }) : null
53383
54472
  ] });
53384
54473
  }
53385
54474
  function FooterHintGroup({ hints, color }) {
53386
54475
  return hints.map((hint, index) => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_react34.default.Fragment, { children: [
53387
54476
  index > 0 ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Text2, { children: " " }) : null,
53388
54477
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Text2, { bold: true, color: color ? hint.tone ?? COLORS.purpleReadable : void 0, children: hint.key }),
53389
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Text2, { color: color ? COLORS.muted : void 0, children: [
53390
- " ",
53391
- hint.label
53392
- ] })
54478
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Text2, { color: color ? COLORS.muted : void 0, children: ` ${hint.label.padEnd(Math.max(hint.label.length, hint.labelWidth ?? 0))}` })
53393
54479
  ] }, `${hint.key}-${hint.label}`));
53394
54480
  }
53395
54481
  function Footer({ rows, usable, color }) {
@@ -53423,6 +54509,15 @@ function HudScreen({ state, columns, rows, color = true, now = Date.now() }) {
53423
54509
  color,
53424
54510
  now
53425
54511
  }
54512
+ ) : state.view === "activity-detail" ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
54513
+ ActivityDetailView,
54514
+ {
54515
+ state,
54516
+ usable: metrics.usable,
54517
+ bodyBudget: metrics.bodyBudget,
54518
+ color,
54519
+ now
54520
+ }
53426
54521
  ) : state.view === "devices" ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
53427
54522
  DevicesView,
53428
54523
  {
@@ -53431,7 +54526,7 @@ function HudScreen({ state, columns, rows, color = true, now = Date.now() }) {
53431
54526
  bodyBudget: metrics.bodyBudget,
53432
54527
  color
53433
54528
  }
53434
- ) : state.view === "workspace" ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
54529
+ ) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
53435
54530
  WorkspaceView,
53436
54531
  {
53437
54532
  state,
@@ -53440,7 +54535,7 @@ function HudScreen({ state, columns, rows, color = true, now = Date.now() }) {
53440
54535
  color,
53441
54536
  now
53442
54537
  }
53443
- ) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)(HelpView, { bodyBudget: metrics.bodyBudget, color }),
54538
+ ),
53444
54539
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Overlay, { state, usable: metrics.usable, color }),
53445
54540
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Footer, { rows: metrics.footerRows, usable: metrics.usable, color })
53446
54541
  ] });
@@ -53452,13 +54547,15 @@ async function loadDevices(store2, actions, signal) {
53452
54547
  ...state,
53453
54548
  view: "devices",
53454
54549
  prompt: void 0,
53455
- notice: void 0
54550
+ notice: void 0,
54551
+ noticeTone: void 0
53456
54552
  }));
53457
54553
  if (current.statusLoading) return;
53458
54554
  if (current.connection !== "connected" && current.connection !== "retrying") {
53459
54555
  store2.update((state) => ({
53460
54556
  ...state,
53461
- notice: "Devices are available after Glossa connects."
54557
+ notice: "Devices are available after Glossa connects.",
54558
+ noticeTone: "info"
53462
54559
  }));
53463
54560
  return;
53464
54561
  }
@@ -53483,7 +54580,8 @@ async function loadDevices(store2, actions, signal) {
53483
54580
  store2.update((state) => ({
53484
54581
  ...state,
53485
54582
  statusLoading: false,
53486
- notice: error46 instanceof Error ? error46.message : String(error46)
54583
+ notice: error46 instanceof Error ? error46.message : String(error46),
54584
+ noticeTone: "error"
53487
54585
  }));
53488
54586
  }
53489
54587
  }
@@ -53491,9 +54589,11 @@ function beginAccessChange(store2, actions, accessProfile) {
53491
54589
  actions.changeAccessProfile(accessProfile);
53492
54590
  store2.update((state) => ({
53493
54591
  ...state,
54592
+ connection: "connecting",
53494
54593
  pendingAccessProfile: accessProfile,
53495
54594
  prompt: void 0,
53496
- notice: void 0
54595
+ notice: void 0,
54596
+ noticeTone: void 0
53497
54597
  }));
53498
54598
  }
53499
54599
  function HudRuntime({ store: store2, actions, signal, stop }) {
@@ -53501,19 +54601,25 @@ function HudRuntime({ store: store2, actions, signal, stop }) {
53501
54601
  const { columns, rows } = use_window_size_default();
53502
54602
  const [, setClock] = (0, import_react34.useState)(0);
53503
54603
  (0, import_react34.useEffect)(() => {
53504
- if (state.view !== "activity" && state.view !== "workspace" || state.activities.length === 0) return;
53505
- const timer = setInterval(() => setClock((value) => value + 1), ACTIVITY_REFRESH_INTERVAL_MS);
54604
+ if (state.view !== "activity" && state.view !== "activity-detail" && state.view !== "workspace" || state.activities.length === 0) return;
54605
+ const hasWorkingActivity = state.activities.some((activity) => activity.state === "working");
54606
+ const interval = hasWorkingActivity ? ACTIVITY_LIVE_REFRESH_INTERVAL_MS : ACTIVITY_IDLE_REFRESH_INTERVAL_MS;
54607
+ const timer = setInterval(() => setClock((value) => value + 1), interval);
53506
54608
  return () => clearInterval(timer);
53507
- }, [state.view, state.activities.length]);
54609
+ }, [state.view, state.activities]);
53508
54610
  (0, import_react34.useEffect)(() => {
53509
54611
  store2.update((current) => {
53510
54612
  let next = current;
53511
54613
  const metrics = screenMetrics(current, columns, rows);
53512
- if (current.view === "activity") {
53513
- const capacity = activityPageCapacity(metrics.bodyBudget);
53514
- const maxPage = activityMaxPage(current.activities.length, capacity);
53515
- if (current.activityPage > maxPage) {
53516
- next = { ...next, activityPage: maxPage };
54614
+ if (current.view === "activity-detail") {
54615
+ const maxScroll = activityDetailMaxScroll(
54616
+ current,
54617
+ metrics.usable,
54618
+ metrics.bodyBudget,
54619
+ Date.now()
54620
+ );
54621
+ if (current.activityDetailScroll > maxScroll) {
54622
+ next = { ...next, activityDetailScroll: maxScroll };
53517
54623
  }
53518
54624
  }
53519
54625
  if (current.prompt?.type === "revoke-confirm") {
@@ -53522,7 +54628,8 @@ function HudRuntime({ store: store2, actions, signal, stop }) {
53522
54628
  next = {
53523
54629
  ...next,
53524
54630
  prompt: void 0,
53525
- notice: "Increase the terminal height to choose a device."
54631
+ notice: "Increase the terminal height to choose a device.",
54632
+ noticeTone: "info"
53526
54633
  };
53527
54634
  }
53528
54635
  }
@@ -53539,7 +54646,12 @@ function HudRuntime({ store: store2, actions, signal, stop }) {
53539
54646
  if (current.busy) return;
53540
54647
  if (current.prompt) {
53541
54648
  if (key.escape || value === "n") {
53542
- store2.update((state2) => ({ ...state2, prompt: void 0, notice: void 0 }));
54649
+ store2.update((state2) => ({
54650
+ ...state2,
54651
+ prompt: void 0,
54652
+ notice: void 0,
54653
+ noticeTone: void 0
54654
+ }));
53543
54655
  return;
53544
54656
  }
53545
54657
  if (value !== "y") return;
@@ -53552,34 +54664,121 @@ function HudRuntime({ store: store2, actions, signal, stop }) {
53552
54664
  store2.update((state2) => ({
53553
54665
  ...state2,
53554
54666
  busy: true,
54667
+ busyMessage: `Revoking ${device.name}\u2026`,
53555
54668
  prompt: void 0,
53556
- notice: void 0
54669
+ notice: void 0,
54670
+ noticeTone: void 0
53557
54671
  }));
53558
54672
  void actions.revokeDevice(device.id, signal).then(async () => {
53559
54673
  if (signal.aborted) return;
53560
- store2.update((state2) => ({ ...state2, busy: false }));
54674
+ store2.update((state2) => ({ ...state2, busy: false, busyMessage: void 0 }));
53561
54675
  await loadDevices(store2, actions, signal);
53562
54676
  if (signal.aborted) return;
53563
- store2.update((state2) => ({ ...state2, notice: `Revoked ${device.name}.` }));
54677
+ store2.update((state2) => ({
54678
+ ...state2,
54679
+ notice: `Revoked ${device.name}.`,
54680
+ noticeTone: "success"
54681
+ }));
53564
54682
  }).catch((error46) => {
53565
54683
  if (signal.aborted) return;
53566
54684
  store2.update((state2) => ({
53567
54685
  ...state2,
53568
54686
  busy: false,
53569
- notice: error46 instanceof Error ? error46.message : String(error46)
54687
+ busyMessage: void 0,
54688
+ notice: error46 instanceof Error ? error46.message : String(error46),
54689
+ noticeTone: "error"
53570
54690
  }));
53571
54691
  });
53572
54692
  return;
53573
54693
  }
53574
- if (current.view === "activity" && (key.upArrow || key.downArrow)) {
53575
- const metrics = screenMetrics(current, columns, rows);
53576
- const capacity = activityPageCapacity(metrics.bodyBudget);
53577
- const maxPage = activityMaxPage(current.activities.length, capacity);
53578
- const page = key.upArrow ? Math.min(maxPage, current.activityPage + 1) : Math.max(0, current.activityPage - 1);
53579
- if (page !== current.activityPage) {
53580
- store2.update((state2) => ({ ...state2, activityPage: page }));
54694
+ if (current.view === "activity-detail") {
54695
+ if (key.upArrow || key.downArrow) {
54696
+ const metrics = screenMetrics(current, columns, rows);
54697
+ const maxScroll = activityDetailMaxScroll(
54698
+ current,
54699
+ metrics.usable,
54700
+ metrics.bodyBudget,
54701
+ Date.now()
54702
+ );
54703
+ const activityDetailScroll = key.upArrow ? Math.max(0, current.activityDetailScroll - 1) : Math.min(maxScroll, current.activityDetailScroll + 1);
54704
+ if (activityDetailScroll !== current.activityDetailScroll) {
54705
+ store2.update((state2) => ({ ...state2, activityDetailScroll }));
54706
+ }
54707
+ return;
54708
+ }
54709
+ if (key.escape || key.return || input === "\r" || input === "\n") {
54710
+ store2.update((state2) => ({
54711
+ ...state2,
54712
+ view: "activity",
54713
+ activityDetailScroll: 0,
54714
+ notice: void 0
54715
+ }));
54716
+ return;
54717
+ }
54718
+ }
54719
+ if (current.view === "activity") {
54720
+ if (key.tab || input === " ") {
54721
+ store2.update((state2) => ({
54722
+ ...state2,
54723
+ activityMode: state2.activityMode === "compact" ? "detailed" : "compact",
54724
+ notice: void 0
54725
+ }));
54726
+ return;
54727
+ }
54728
+ if (key.escape && current.activitySelection) {
54729
+ store2.update((state2) => ({
54730
+ ...state2,
54731
+ activitySelection: void 0,
54732
+ notice: void 0
54733
+ }));
54734
+ return;
54735
+ }
54736
+ if (key.upArrow || key.downArrow) {
54737
+ const metrics = screenMetrics(current, columns, rows);
54738
+ if (current.activitySelection) {
54739
+ const update = activitySelectionUpdate(
54740
+ current,
54741
+ metrics.bodyBudget,
54742
+ key.upArrow ? -1 : 1
54743
+ );
54744
+ if (update) {
54745
+ store2.update((state2) => ({ ...state2, ...update, notice: void 0 }));
54746
+ }
54747
+ } else {
54748
+ const update = activityPageUpdate(
54749
+ current,
54750
+ metrics.bodyBudget,
54751
+ key.upArrow ? "older" : "newer"
54752
+ );
54753
+ if (update) {
54754
+ store2.update((state2) => ({ ...state2, ...update, notice: void 0 }));
54755
+ }
54756
+ }
54757
+ return;
54758
+ }
54759
+ if (key.return || input === "\r" || input === "\n") {
54760
+ if (!current.activitySelection) {
54761
+ const metrics = screenMetrics(current, columns, rows);
54762
+ const activitySelection = activityCenterSelection(current, metrics.bodyBudget);
54763
+ if (activitySelection) {
54764
+ store2.update((state2) => ({
54765
+ ...state2,
54766
+ activitySelection,
54767
+ notice: void 0
54768
+ }));
54769
+ }
54770
+ return;
54771
+ }
54772
+ const selected = selectedActivity(current);
54773
+ if (!selected) return;
54774
+ store2.update((state2) => ({
54775
+ ...state2,
54776
+ view: "activity-detail",
54777
+ activityDetailScroll: 0,
54778
+ notice: void 0
54779
+ }));
54780
+ return;
53581
54781
  }
53582
- return;
53583
54782
  }
53584
54783
  if (current.view === "devices" && (key.upArrow || key.downArrow)) {
53585
54784
  const total = current.status?.devices.length ?? 0;
@@ -53593,7 +54792,7 @@ function HudRuntime({ store: store2, actions, signal, stop }) {
53593
54792
  }
53594
54793
  return;
53595
54794
  }
53596
- if (current.view === "workspace" && current.accessProfile && !current.pendingAccessProfile && (key.leftArrow || key.rightArrow)) {
54795
+ if (current.view === "workspace" && current.connection === "connected" && current.accessProfile && !current.pendingAccessProfile && (key.leftArrow || key.rightArrow)) {
53597
54796
  const direction = key.leftArrow ? -1 : 1;
53598
54797
  const accessProfile = adjacentAccessProfile(current.accessProfile, direction);
53599
54798
  if (!accessProfile) return;
@@ -53613,10 +54812,13 @@ function HudRuntime({ store: store2, actions, signal, stop }) {
53613
54812
  return;
53614
54813
  }
53615
54814
  if (value === "a") {
54815
+ const preserveBrowsePage = current.view === "activity" || current.view === "activity-detail";
53616
54816
  store2.update((state2) => ({
53617
54817
  ...state2,
53618
54818
  view: "activity",
53619
- activityPage: current.view === "activity" ? state2.activityPage : 0,
54819
+ activitySelection: void 0,
54820
+ activityBrowseAnchor: preserveBrowsePage ? state2.activityBrowseAnchor : void 0,
54821
+ activityDetailScroll: 0,
53620
54822
  notice: void 0
53621
54823
  }));
53622
54824
  return;
@@ -53646,27 +54848,29 @@ function HudRuntime({ store: store2, actions, signal, stop }) {
53646
54848
  if ((key.return || input === "\r" || input === "\n" || value === "r") && current.view === "devices") {
53647
54849
  const count = current.status?.devices.length ?? 0;
53648
54850
  if (count === 0) {
53649
- store2.update((state2) => ({ ...state2, notice: "There are no devices to revoke." }));
54851
+ store2.update((state2) => ({
54852
+ ...state2,
54853
+ notice: "There are no paired devices to revoke.",
54854
+ noticeTone: "info"
54855
+ }));
53650
54856
  return;
53651
54857
  }
53652
54858
  const metrics = screenMetrics(current, columns, rows);
53653
54859
  const capacity = statusDeviceCapacity(current, metrics.bodyBudget, metrics.usable);
53654
54860
  const selection = Math.min(current.deviceSelection, count - 1);
53655
- store2.update((state2) => capacity === 0 ? { ...state2, notice: "Increase the terminal height to choose a device." } : {
54861
+ store2.update((state2) => capacity === 0 ? {
54862
+ ...state2,
54863
+ notice: "Increase the terminal height to choose a device.",
54864
+ noticeTone: "info"
54865
+ } : {
53656
54866
  ...state2,
53657
54867
  deviceSelection: selection,
53658
54868
  prompt: { type: "revoke-confirm", deviceIndex: selection },
53659
- notice: void 0
54869
+ notice: void 0,
54870
+ noticeTone: void 0
53660
54871
  });
53661
54872
  return;
53662
54873
  }
53663
- if (input === "?") {
53664
- store2.update((state2) => ({
53665
- ...state2,
53666
- view: "help",
53667
- notice: void 0
53668
- }));
53669
- }
53670
54874
  });
53671
54875
  return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
53672
54876
  HudScreen,
@@ -53684,7 +54888,7 @@ async function runSessionHud(actions, input = process.stdin, output = process.st
53684
54888
  throw new Error("Glossa requires an interactive terminal.");
53685
54889
  }
53686
54890
  const controller = new AbortController();
53687
- const store2 = new HudStore(actions.workspace);
54891
+ const store2 = new HudStore(actions.workspace, actions.initialNotice);
53688
54892
  let instance;
53689
54893
  const stop = () => {
53690
54894
  controller.abort();
@@ -53745,8 +54949,8 @@ async function runSessionHud(actions, input = process.stdin, output = process.st
53745
54949
  var import_semver = __toESM(require_semver2(), 1);
53746
54950
  import { spawn as spawn2 } from "node:child_process";
53747
54951
  import { createHash as createHash2 } from "node:crypto";
53748
- import { chmod as chmod3, readdir as readdir2, rename as rename2, rm as rm5, writeFile as writeFile6 } from "node:fs/promises";
53749
- import path8 from "node:path";
54952
+ import { chmod as chmod4, readdir as readdir2, rename as rename3, rm as rm6, writeFile as writeFile7 } from "node:fs/promises";
54953
+ import path9 from "node:path";
53750
54954
  var PACKAGE_NAME = "@ariobarin/glossa";
53751
54955
  var DEFAULT_REGISTRY_URL = "https://registry.npmjs.org/@ariobarin%2Fglossa";
53752
54956
  var DEFAULT_RELEASE_BASE_URL = "https://github.com/ariobarin/glossa/releases/download";
@@ -53823,8 +55027,8 @@ async function cleanupUpdateBackups(distribution, options = {}) {
53823
55027
  const platform2 = options.platform ?? process.platform;
53824
55028
  if (distribution !== "standalone" || platform2 !== "win32") return;
53825
55029
  const executable = options.executablePath ?? process.execPath;
53826
- const directory = path8.dirname(executable);
53827
- const prefix = `${path8.basename(executable)}.old-`;
55030
+ const directory = path9.dirname(executable);
55031
+ const prefix = `${path9.basename(executable)}.old-`;
53828
55032
  let entries;
53829
55033
  try {
53830
55034
  entries = await readdir2(directory, { withFileTypes: true });
@@ -53837,7 +55041,7 @@ async function cleanupUpdateBackups(distribution, options = {}) {
53837
55041
  const ownerPid = backupOwnerPid(entry.name, prefix);
53838
55042
  return ownerPid !== null && !processIsAlive(ownerPid);
53839
55043
  }).map(async (entry) => {
53840
- await rm5(path8.join(directory, entry.name), { force: true }).catch(() => void 0);
55044
+ await rm6(path9.join(directory, entry.name), { force: true }).catch(() => void 0);
53841
55045
  })
53842
55046
  );
53843
55047
  }
@@ -53888,11 +55092,11 @@ async function runNpmInstall(version2) {
53888
55092
  }
53889
55093
  async function replaceWindowsExecutable(download, destination) {
53890
55094
  const backup = `${destination}.old-${process.pid}-${Date.now()}`;
53891
- await rename2(destination, backup);
55095
+ await rename3(destination, backup);
53892
55096
  try {
53893
- await rename2(download, destination);
55097
+ await rename3(download, destination);
53894
55098
  } catch (error46) {
53895
- await rename2(backup, destination).catch(() => void 0);
55099
+ await rename3(backup, destination).catch(() => void 0);
53896
55100
  throw error46;
53897
55101
  }
53898
55102
  }
@@ -53913,21 +55117,21 @@ async function installStandaloneUpdate(info, options) {
53913
55117
  if (actual !== expected) {
53914
55118
  throw new Error("Glossa refused to update because the SHA-256 checksum did not match.");
53915
55119
  }
53916
- const download = path8.join(
53917
- path8.dirname(destination),
53918
- `${path8.basename(destination)}.download-${process.pid}`
55120
+ const download = path9.join(
55121
+ path9.dirname(destination),
55122
+ `${path9.basename(destination)}.download-${process.pid}`
53919
55123
  );
53920
55124
  try {
53921
- await writeFile6(download, binary, { mode: 493 });
55125
+ await writeFile7(download, binary, { mode: 493 });
53922
55126
  if (platform2 === "win32") {
53923
55127
  await replaceWindowsExecutable(download, destination);
53924
55128
  } else {
53925
- await chmod3(download, 493);
53926
- await rename2(download, destination);
55129
+ await chmod4(download, 493);
55130
+ await rename3(download, destination);
53927
55131
  }
53928
55132
  return "installed";
53929
55133
  } catch (error46) {
53930
- await rm5(download, { force: true });
55134
+ await rm6(download, { force: true });
53931
55135
  throw error46;
53932
55136
  }
53933
55137
  }
@@ -53940,84 +55144,6 @@ async function installUpdate(info, distribution, options = {}) {
53940
55144
  return await installStandaloneUpdate(info, options);
53941
55145
  }
53942
55146
 
53943
- // src/update-state.ts
53944
- import { chmod as chmod4, mkdir as mkdir6, readFile as readFile5, rename as rename3, rm as rm6, writeFile as writeFile7 } from "node:fs/promises";
53945
- import path9 from "node:path";
53946
- var UPDATE_CHECK_INTERVAL_MS = 24 * 60 * 60 * 1e3;
53947
- function defaultUpdateChannel(version2) {
53948
- return version2.includes("-") ? "beta" : "stable";
53949
- }
53950
- function updateStateFile() {
53951
- return path9.join(configDirectory(), "updates.json");
53952
- }
53953
- function isPolicy(value) {
53954
- return value === "notify" || value === "auto" || value === "off";
53955
- }
53956
- function isChannel(value) {
53957
- return value === "beta" || value === "stable";
53958
- }
53959
- function optionalString(value) {
53960
- return typeof value === "string" && value.length > 0 ? value : void 0;
53961
- }
53962
- async function loadUpdateState(currentVersion, file2 = updateStateFile()) {
53963
- const defaults = {
53964
- policy: "notify",
53965
- channel: defaultUpdateChannel(currentVersion)
53966
- };
53967
- try {
53968
- const parsed = JSON.parse(await readFile5(file2, "utf8"));
53969
- const lastCheckedAt = optionalString(parsed.lastCheckedAt);
53970
- return {
53971
- policy: isPolicy(parsed.policy) ? parsed.policy : defaults.policy,
53972
- channel: isChannel(parsed.channel) ? parsed.channel : defaults.channel,
53973
- ...lastCheckedAt ? { lastCheckedAt } : {}
53974
- };
53975
- } catch (error46) {
53976
- if (error46.code === "ENOENT" || error46 instanceof SyntaxError) {
53977
- return defaults;
53978
- }
53979
- throw error46;
53980
- }
53981
- }
53982
- async function saveUpdateState(state, file2 = updateStateFile()) {
53983
- const directory = path9.dirname(file2);
53984
- const temporary = `${file2}.${process.pid}.tmp`;
53985
- await mkdir6(directory, { recursive: true, mode: 448 });
53986
- try {
53987
- await writeFile7(temporary, `${JSON.stringify(state, null, 2)}
53988
- `, {
53989
- encoding: "utf8",
53990
- mode: 384
53991
- });
53992
- if (process.platform !== "win32") await chmod4(temporary, 384);
53993
- await rename3(temporary, file2);
53994
- } finally {
53995
- await rm6(temporary, { force: true });
53996
- }
53997
- }
53998
- async function configureUpdates(currentVersion, changes, file2 = updateStateFile()) {
53999
- const previous = await loadUpdateState(currentVersion, file2);
54000
- const state = {
54001
- policy: changes.policy ?? previous.policy,
54002
- channel: changes.channel ?? previous.channel
54003
- };
54004
- await saveUpdateState(state, file2);
54005
- return state;
54006
- }
54007
- async function recordUpdateCheck(currentVersion, checkedAt = /* @__PURE__ */ new Date(), file2 = updateStateFile()) {
54008
- const state = {
54009
- ...await loadUpdateState(currentVersion, file2),
54010
- lastCheckedAt: checkedAt.toISOString()
54011
- };
54012
- await saveUpdateState(state, file2);
54013
- return state;
54014
- }
54015
- function isUpdateCheckDue(lastCheckedAt, now = Date.now()) {
54016
- if (!lastCheckedAt) return true;
54017
- const checkedAt = Date.parse(lastCheckedAt);
54018
- return !Number.isFinite(checkedAt) || now - checkedAt >= UPDATE_CHECK_INTERVAL_MS;
54019
- }
54020
-
54021
55147
  // src/unpair.ts
54022
55148
  async function unpairComputer(dependencies = {}) {
54023
55149
  const load = dependencies.loadDeviceCredential ?? loadDeviceCredential;
@@ -54227,7 +55353,7 @@ async function acquireWorkspaceLease(root, options = {}) {
54227
55353
  }
54228
55354
 
54229
55355
  // src/main.ts
54230
- var VERSION = "0.2.3-beta.1";
55356
+ var VERSION = "0.2.4";
54231
55357
  var DISTRIBUTION = "npm";
54232
55358
  var HELP = `Glossa ${VERSION}
54233
55359
 
@@ -54250,13 +55376,12 @@ Keys:
54250
55376
  a activity
54251
55377
  w workspace
54252
55378
  d devices
54253
- \u2191\u2193 select or browse
55379
+ \u2191\u2193 browse or select the current list
54254
55380
  \u2190\u2192 change workspace access
54255
- Enter/r revoke selected device
54256
- Esc workspace
54257
- ? help
55381
+ Enter select, inspect, or revoke
55382
+ Esc back to the previous view or workspace
54258
55383
  q disconnect and quit`;
54259
- async function runWorkspaceSession(path12, label, accessProfile) {
55384
+ async function runWorkspaceSession(path12, label, accessProfile, initialNotice) {
54260
55385
  const root = await selectExposureRoot(path12);
54261
55386
  const lease = await acquireWorkspaceLease(root);
54262
55387
  try {
@@ -54268,6 +55393,7 @@ async function runWorkspaceSession(path12, label, accessProfile) {
54268
55393
  await runSessionHud({
54269
55394
  workspace: root,
54270
55395
  ...label ? { workspaceLabel: label } : {},
55396
+ ...initialNotice ? { initialNotice } : {},
54271
55397
  run: async (signal, onEvent) => {
54272
55398
  while (!signal.aborted) {
54273
55399
  const sessionAccessProfile = requestedAccessProfile;
@@ -54316,7 +55442,8 @@ async function runWorkspaceSession(path12, label, accessProfile) {
54316
55442
  name: entry.name,
54317
55443
  platform: entry.platform ?? "Unknown platform",
54318
55444
  lastSeen: formatRelativeTime(entry.lastSeenAt),
54319
- status: deviceStatus(entry)
55445
+ status: deviceStatus(entry),
55446
+ ...entry.id === device.deviceId ? { current: true } : {}
54320
55447
  }))
54321
55448
  };
54322
55449
  },
@@ -54339,9 +55466,9 @@ async function runWorkspaceSession(path12, label, accessProfile) {
54339
55466
  await lease.release();
54340
55467
  }
54341
55468
  }
54342
- async function runWorkspace(path12, label, accessProfile) {
55469
+ async function runWorkspace(path12, label, accessProfile, initialNotice) {
54343
55470
  await withWorkspaceLease(
54344
- async () => await runWorkspaceSession(path12, label, accessProfile)
55471
+ async () => await runWorkspaceSession(path12, label, accessProfile, initialNotice)
54345
55472
  );
54346
55473
  }
54347
55474
  async function refreshUpdateInfo(timeoutMs) {
@@ -54386,7 +55513,7 @@ async function runUpdateCommand(invocation) {
54386
55513
  async function updateBeforeWorkspace() {
54387
55514
  const state = await loadUpdateState(VERSION);
54388
55515
  if (state.policy === "off" || !isUpdateCheckDue(state.lastCheckedAt)) {
54389
- return false;
55516
+ return { exit: false };
54390
55517
  }
54391
55518
  let info;
54392
55519
  try {
@@ -54398,18 +55525,18 @@ async function updateBeforeWorkspace() {
54398
55525
  `Glossa could not check for an automatic update: ${message} Continuing with ${VERSION}.`
54399
55526
  );
54400
55527
  }
54401
- return false;
55528
+ return { exit: false };
54402
55529
  }
54403
55530
  if (!info.updateAvailable) {
54404
55531
  await recordUpdateCheck(VERSION);
54405
- return false;
55532
+ return { exit: false };
54406
55533
  }
54407
55534
  if (state.policy === "notify") {
54408
55535
  await recordUpdateCheck(VERSION);
54409
- console.error(
54410
- `Glossa ${info.availableVersion} is available. Run glossa update after disconnecting.`
54411
- );
54412
- return false;
55536
+ return {
55537
+ exit: false,
55538
+ notice: `Glossa ${info.availableVersion} is available. Run glossa update after disconnecting.`
55539
+ };
54413
55540
  }
54414
55541
  try {
54415
55542
  console.error(`Updating Glossa ${VERSION} to ${info.availableVersion}...`);
@@ -54417,13 +55544,13 @@ async function updateBeforeWorkspace() {
54417
55544
  async () => await installUpdate(info, DISTRIBUTION)
54418
55545
  );
54419
55546
  console.error(`Updated Glossa to ${info.availableVersion}. Run glossa again.`);
54420
- return true;
55547
+ return { exit: true };
54421
55548
  } catch (error46) {
54422
55549
  const message = error46 instanceof Error ? error46.message : String(error46);
54423
55550
  console.error(
54424
55551
  `Glossa could not update automatically: ${message} Continuing with ${VERSION}.`
54425
55552
  );
54426
- return false;
55553
+ return { exit: false };
54427
55554
  }
54428
55555
  }
54429
55556
  async function main() {
@@ -54434,11 +55561,13 @@ async function main() {
54434
55561
  } else if (invocation.command === "version") {
54435
55562
  console.log(VERSION);
54436
55563
  } else if (invocation.command === "workspace") {
54437
- if (await updateBeforeWorkspace()) return;
55564
+ const update = await updateBeforeWorkspace();
55565
+ if (update.exit) return;
54438
55566
  await runWorkspace(
54439
55567
  invocation.path,
54440
55568
  invocation.label,
54441
- invocation.accessProfile
55569
+ invocation.accessProfile,
55570
+ update.notice
54442
55571
  );
54443
55572
  } else if (invocation.command === "unpair") {
54444
55573
  await unpairComputer();