@phnx-labs/agents-cli 1.22.23 → 1.22.25
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +359 -0
- package/README.md +19 -8
- package/dist/bin/agents +0 -0
- package/dist/commands/attach.d.ts +2 -0
- package/dist/commands/attach.js +32 -7
- package/dist/commands/cloud.js +9 -5
- package/dist/commands/defaults.js +2 -0
- package/dist/commands/doctor.d.ts +24 -0
- package/dist/commands/doctor.js +105 -9
- package/dist/commands/exec.js +99 -44
- package/dist/commands/feed.d.ts +23 -0
- package/dist/commands/feed.js +65 -2
- package/dist/commands/focus.d.ts +34 -3
- package/dist/commands/focus.js +301 -57
- package/dist/commands/go.d.ts +14 -1
- package/dist/commands/go.js +49 -5
- package/dist/commands/insights.js +6 -2
- package/dist/commands/inspect.js +39 -5
- package/dist/commands/menubar.js +14 -1
- package/dist/commands/models.js +1 -0
- package/dist/commands/modes.d.ts +12 -0
- package/dist/commands/modes.js +147 -0
- package/dist/commands/routines.d.ts +3 -0
- package/dist/commands/routines.js +70 -47
- package/dist/commands/run-cloud.js +1 -1
- package/dist/commands/secrets-sync.js +11 -13
- package/dist/commands/secrets.d.ts +2 -0
- package/dist/commands/secrets.js +54 -17
- package/dist/commands/sessions-browser.d.ts +36 -1
- package/dist/commands/sessions-browser.js +165 -19
- package/dist/commands/sessions-resume.d.ts +2 -0
- package/dist/commands/sessions-resume.js +28 -12
- package/dist/commands/sessions.d.ts +23 -1
- package/dist/commands/sessions.js +58 -61
- package/dist/commands/setup-secrets.js +1 -1
- package/dist/commands/setup-watchdog.js +5 -10
- package/dist/commands/setup.js +1 -1
- package/dist/commands/sync.js +246 -42
- package/dist/commands/teams.js +3 -2
- package/dist/commands/view.js +2 -0
- package/dist/commands/watchdog.d.ts +3 -4
- package/dist/commands/watchdog.js +26 -66
- package/dist/index.js +38 -2
- package/dist/lib/agent-modes.d.ts +49 -0
- package/dist/lib/agent-modes.js +70 -0
- package/dist/lib/agents.js +126 -21
- package/dist/lib/cloud/cursor.d.ts +79 -0
- package/dist/lib/cloud/cursor.js +228 -0
- package/dist/lib/cloud/registry.js +2 -0
- package/dist/lib/cloud/types.d.ts +7 -2
- package/dist/lib/cloud/types.js +14 -0
- package/dist/lib/crabbox/cli.d.ts +2 -2
- package/dist/lib/crabbox/config.d.ts +7 -8
- package/dist/lib/crabbox/config.js +14 -14
- package/dist/lib/crabbox/lease.d.ts +11 -4
- package/dist/lib/crabbox/lease.js +40 -8
- package/dist/lib/crabbox/setup-copy.d.ts +5 -0
- package/dist/lib/crabbox/setup-copy.js +17 -1
- package/dist/lib/daemon.js +27 -1
- package/dist/lib/device-config.js +7 -0
- package/dist/lib/devices/doctor-findings.d.ts +7 -1
- package/dist/lib/devices/doctor-findings.js +40 -1
- package/dist/lib/doctor-diff.d.ts +3 -0
- package/dist/lib/doctor-diff.js +15 -13
- package/dist/lib/event-stream.d.ts +3 -1
- package/dist/lib/event-stream.js +14 -1
- package/dist/lib/events.d.ts +9 -0
- package/dist/lib/events.js +58 -0
- package/dist/lib/exec.d.ts +15 -3
- package/dist/lib/exec.js +112 -19
- package/dist/lib/feed-outcome.d.ts +3 -0
- package/dist/lib/feed-outcome.js +18 -10
- package/dist/lib/feed.d.ts +4 -0
- package/dist/lib/hooks/cache.js +36 -3
- package/dist/lib/hooks.d.ts +19 -7
- package/dist/lib/hooks.js +88 -38
- package/dist/lib/hosts/passthrough.d.ts +21 -0
- package/dist/lib/hosts/passthrough.js +39 -12
- package/dist/lib/hosts/session-index.d.ts +4 -0
- package/dist/lib/hosts/session-index.js +7 -0
- package/dist/lib/manifest.d.ts +12 -2
- package/dist/lib/manifest.js +60 -5
- package/dist/lib/mcp.js +5 -1
- package/dist/lib/menubar/MenubarHelper.app/Contents/CodeResources +0 -0
- package/dist/lib/menubar/MenubarHelper.app/Contents/Info.plist +5 -1
- package/dist/lib/menubar/MenubarHelper.app/Contents/MacOS/MenubarHelper +0 -0
- package/dist/lib/menubar/MenubarHelper.app/Contents/Resources/AppIcon.icns +0 -0
- package/dist/lib/menubar/MenubarHelper.app/Contents/_CodeSignature/CodeResources +15 -2
- package/dist/lib/menubar/install-menubar.d.ts +52 -2
- package/dist/lib/menubar/install-menubar.js +128 -6
- package/dist/lib/menubar/snapshot.d.ts +15 -0
- package/dist/lib/menubar/snapshot.js +40 -0
- package/dist/lib/plugins.js +13 -1
- package/dist/lib/refresh.d.ts +5 -0
- package/dist/lib/refresh.js +37 -33
- package/dist/lib/resource-inventory.d.ts +79 -0
- package/dist/lib/resource-inventory.js +122 -0
- package/dist/lib/resources/mcp.js +3 -0
- package/dist/lib/resources.js +8 -5
- package/dist/lib/routine-process-cleanup.d.ts +9 -0
- package/dist/lib/routine-process-cleanup.js +73 -0
- package/dist/lib/run-defaults.d.ts +2 -0
- package/dist/lib/run-defaults.js +23 -2
- package/dist/lib/runner.js +27 -22
- package/dist/lib/secrets/Agents CLI.app/Contents/CodeResources +0 -0
- package/dist/lib/secrets/Agents CLI.app/Contents/Info.plist +2 -0
- package/dist/lib/secrets/Agents CLI.app/Contents/MacOS/Agents CLI +0 -0
- package/dist/lib/secrets/Agents CLI.app/Contents/Resources/AppIcon.icns +0 -0
- package/dist/lib/secrets/Agents CLI.app/Contents/_CodeSignature/CodeResources +13 -1
- package/dist/lib/secrets/bundles.js +158 -85
- package/dist/lib/secrets/filestore.d.ts +7 -3
- package/dist/lib/secrets/filestore.js +25 -8
- package/dist/lib/secrets/index.js +34 -8
- package/dist/lib/secrets/rc-hygiene.d.ts +14 -0
- package/dist/lib/secrets/rc-hygiene.js +14 -1
- package/dist/lib/secrets/reaper.d.ts +10 -0
- package/dist/lib/secrets/reaper.js +50 -15
- package/dist/lib/secrets/sync-passphrase.d.ts +27 -0
- package/dist/lib/secrets/sync-passphrase.js +78 -0
- package/dist/lib/session/active.d.ts +4 -0
- package/dist/lib/session/db.js +8 -2
- package/dist/lib/session/recovery.d.ts +37 -0
- package/dist/lib/session/recovery.js +95 -0
- package/dist/lib/session/remote-list.d.ts +2 -0
- package/dist/lib/session/remote-list.js +1 -0
- package/dist/lib/session/session-cache.d.ts +4 -4
- package/dist/lib/session/session-cache.js +4 -4
- package/dist/lib/shims.d.ts +2 -2
- package/dist/lib/shims.js +45 -6
- package/dist/lib/signin-badge.js +2 -0
- package/dist/lib/startup/command-registry.d.ts +16 -0
- package/dist/lib/startup/command-registry.js +44 -0
- package/dist/lib/sync-umbrella.d.ts +5 -0
- package/dist/lib/sync-umbrella.js +5 -4
- package/dist/lib/teams/agents.js +1 -1
- package/dist/lib/teams/parsers.d.ts +1 -1
- package/dist/lib/tmux/session.d.ts +4 -2
- package/dist/lib/tmux/session.js +5 -5
- package/dist/lib/types.d.ts +3 -1
- package/dist/lib/versions.js +16 -1
- package/dist/lib/watchdog/service.d.ts +17 -0
- package/dist/lib/watchdog/service.js +39 -0
- package/package.json +1 -1
package/dist/commands/secrets.js
CHANGED
|
@@ -31,6 +31,7 @@ import { readMeta } from '../lib/state.js';
|
|
|
31
31
|
import { parseDuration } from '../lib/hooks/cache.js';
|
|
32
32
|
import { emit, query } from '../lib/events.js';
|
|
33
33
|
import { emitSecretAudit } from '../lib/secrets/audit.js';
|
|
34
|
+
import { SYNC_PASSPHRASE_ENV, resolveSyncPassphraseFromEnv } from '../lib/secrets/sync-passphrase.js';
|
|
34
35
|
import { getBundleUsage, getAllBundleUsage, getUsageHistory, SECRET_USAGE_EVENTS, } from '../lib/secrets/usage-db.js';
|
|
35
36
|
import { frequentlyPromptedBundles } from '../lib/secrets/unlock-hints.js';
|
|
36
37
|
import { registerCommandGroups, setHelpSections } from '../lib/help.js';
|
|
@@ -266,11 +267,31 @@ function maybePrintSyncedHint(name, stillPresent) {
|
|
|
266
267
|
* defaults). Shared with the command action so the wiring is unit-testable
|
|
267
268
|
* without a live SSH session.
|
|
268
269
|
*/
|
|
270
|
+
export function resolveUnlockTtlMs(ttl, until, now = Date.now()) {
|
|
271
|
+
if (ttl && until)
|
|
272
|
+
throw new Error('--ttl and --until are mutually exclusive.');
|
|
273
|
+
if (until) {
|
|
274
|
+
const expiresAt = Date.parse(until);
|
|
275
|
+
if (!Number.isFinite(expiresAt))
|
|
276
|
+
throw new Error("Invalid --until '" + until + "'. Use an ISO date or timestamp.");
|
|
277
|
+
if (expiresAt <= now)
|
|
278
|
+
throw new Error("Invalid --until '" + until + "': date must be in the future.");
|
|
279
|
+
return expiresAt - now;
|
|
280
|
+
}
|
|
281
|
+
if (ttl) {
|
|
282
|
+
const secs = parseDuration(ttl);
|
|
283
|
+
if (!secs)
|
|
284
|
+
throw new Error("Invalid --ttl '" + ttl + "'. Use e.g. 30m, 2h, 8h, 3d.");
|
|
285
|
+
return secs * 1000;
|
|
286
|
+
}
|
|
287
|
+
return secretsHoldMs();
|
|
288
|
+
}
|
|
269
289
|
export function buildRemoteUnlockArgs(names, opts) {
|
|
270
290
|
return [
|
|
271
291
|
'unlock',
|
|
272
292
|
...(opts.all ? ['--all'] : names),
|
|
273
293
|
...(opts.ttl ? ['--ttl', opts.ttl] : []),
|
|
294
|
+
...(opts.until ? ['--until', opts.until] : []),
|
|
274
295
|
// Forward --durable so a remote unlock honors it too; without this the remote
|
|
275
296
|
// silently falls back to its own secrets.agent.durable default (off).
|
|
276
297
|
...(opts.durable ? ['--durable'] : []),
|
|
@@ -1957,7 +1978,7 @@ Examples:
|
|
|
1957
1978
|
.option('--synced', 'When creating the bundle, store it in the age-encrypted synced secrets file')
|
|
1958
1979
|
.option('--force', 'Overwrite an existing key in the bundle')
|
|
1959
1980
|
.option('--purge', 'With --from icloud: delete the iCloud copies after a successful import (iCloud propagates the deletion to your other devices)')
|
|
1960
|
-
.option('--from-file <path>',
|
|
1981
|
+
.option('--from-file <path>', `Import from an AES-256-GCM encrypted offline bundle file (needs ${SYNC_PASSPHRASE_ENV}; symmetric counterpart of export --to-file)`)
|
|
1961
1982
|
.option('--from-ssh', 'Pull the bundle from a fleet peer over SSH and import it locally (requires --host)')
|
|
1962
1983
|
.option('--host <peer>', 'SSH peer to pull from when using --from-ssh (host alias or user@host)')
|
|
1963
1984
|
.action(async (bundleName, opts) => {
|
|
@@ -1973,9 +1994,10 @@ Examples:
|
|
|
1973
1994
|
throw new Error('--from-file, --from-ssh, and --from/--from-1password are mutually exclusive; pick one import source.');
|
|
1974
1995
|
}
|
|
1975
1996
|
if (opts.fromFile) {
|
|
1976
|
-
|
|
1997
|
+
// Transport, not the local store's master key — see lib/secrets/sync-passphrase.ts.
|
|
1998
|
+
const passphrase = resolveSyncPassphraseFromEnv().value ?? '';
|
|
1977
1999
|
if (!passphrase) {
|
|
1978
|
-
throw new Error(
|
|
2000
|
+
throw new Error(`--from-file needs ${SYNC_PASSPHRASE_ENV} set to decrypt the bundle file.`);
|
|
1979
2001
|
}
|
|
1980
2002
|
const env = importBundleFromFile(opts.fromFile, passphrase);
|
|
1981
2003
|
const resolvedBundleName = bundleName ?? (await pickBundleName('import into'));
|
|
@@ -2063,7 +2085,7 @@ Examples:
|
|
|
2063
2085
|
.option('--remote-backend <backend>', 'Backend for the bundle on the remote (with --host): keychain (default) or file. file is headless-readable via the remote\'s machine-local key; it forwards AGENTS_SECRETS_PASSPHRASE over stdin only if set (opt-in).', 'keychain')
|
|
2064
2086
|
.option('--force', 'Overwrite existing keys/items on the target (used with --to-1password and --host)')
|
|
2065
2087
|
.option('--format <shell|json>', 'Output for --plaintext export: shell (default) or json (lossless, machine-readable; used by remote resolve)', 'shell')
|
|
2066
|
-
.option('--to-file <path>',
|
|
2088
|
+
.option('--to-file <path>', `Write the bundle as an AES-256-GCM encrypted offline file (needs ${SYNC_PASSPHRASE_ENV}; symmetric counterpart of import --from-file)`)
|
|
2067
2089
|
.action(async (bundleName, opts) => {
|
|
2068
2090
|
try {
|
|
2069
2091
|
// `--device` is an alias for `--host` (fleet vocabulary parity with
|
|
@@ -2074,9 +2096,10 @@ Examples:
|
|
|
2074
2096
|
const { readAndResolveBundleEnv, bundleToEnvPrefix, isReservedEnvName } = await import('../lib/secrets/bundles.js');
|
|
2075
2097
|
const resolvedBundleName = bundleName ?? (await pickBundleName('export'));
|
|
2076
2098
|
if (opts.toFile) {
|
|
2077
|
-
|
|
2099
|
+
// Transport, not the local store's master key — see lib/secrets/sync-passphrase.ts.
|
|
2100
|
+
const passphrase = resolveSyncPassphraseFromEnv().value ?? '';
|
|
2078
2101
|
if (!passphrase) {
|
|
2079
|
-
throw new Error(
|
|
2102
|
+
throw new Error(`--to-file needs ${SYNC_PASSPHRASE_ENV} set to encrypt the bundle. ` +
|
|
2080
2103
|
'Set it for this command, then supply the same value when importing.');
|
|
2081
2104
|
}
|
|
2082
2105
|
const { env } = readAndResolveBundleEnv(resolvedBundleName, { caller: 'export --to-file', keyMode: 'storage', agentOnly: true });
|
|
@@ -2483,11 +2506,16 @@ Examples:
|
|
|
2483
2506
|
.command('unlock [names...]')
|
|
2484
2507
|
.description('Hold a bundle in the secrets-agent after one Touch ID, so concurrent runs read it without re-prompting (macOS). With --host, unlock FILE-backed bundle(s) on a remote (the passphrase prompt surfaces over the SSH TTY); keychain/biometry bundles are GUI-only and can\'t be remote-unlocked.')
|
|
2485
2508
|
.option('--ttl <duration>', 'How long to hold it (e.g. 30m, 8h, 3d). Default 7d.')
|
|
2509
|
+
.option('--until <date>', 'Hold until this absolute date or timestamp (for example 2026-08-06T12:00:00Z). Mutually exclusive with --ttl.')
|
|
2486
2510
|
.option('--durable', 'Keep the unlock across sleep + reboot too (default: survives upgrade/restart but re-locks on sleep). Set secrets.agent.durable in agents.yaml to make this the default.')
|
|
2487
2511
|
.option('--for <agent>', 'Narrow the unlock to ONE harness type (for example claude, codex, or kimi). Default: the grant is global — every harness and a plain shell can read it, so one Touch ID covers them all.')
|
|
2488
2512
|
.option('--all', 'Unlock every configured bundle')
|
|
2489
2513
|
.option('--host <target>', 'Unlock the bundle(s) on this remote machine over SSH instead of locally (file-backed bundles only — the remote\'s passphrase prompt surfaces on your terminal over a -tt session). Single-valued (NOT variadic) so it never swallows the bundle name: `unlock <name> --host <machine>`.')
|
|
2490
2514
|
.action(async (names, opts) => {
|
|
2515
|
+
if (opts.ttl && opts.until) {
|
|
2516
|
+
console.error(chalk.red('--ttl and --until are mutually exclusive.'));
|
|
2517
|
+
process.exit(1);
|
|
2518
|
+
}
|
|
2491
2519
|
// Single-valued (not variadic): a variadic --host greedily consumes the
|
|
2492
2520
|
// positional bundle name (`unlock --host mac wztest` -> host=[mac,wztest],
|
|
2493
2521
|
// names=[]). Unlock targets one remote at a time anyway.
|
|
@@ -2524,6 +2552,15 @@ Examples:
|
|
|
2524
2552
|
process.exit(1);
|
|
2525
2553
|
return;
|
|
2526
2554
|
}
|
|
2555
|
+
if (opts.until) {
|
|
2556
|
+
try {
|
|
2557
|
+
resolveUnlockTtlMs(undefined, opts.until);
|
|
2558
|
+
}
|
|
2559
|
+
catch (err) {
|
|
2560
|
+
console.error(chalk.red(err.message));
|
|
2561
|
+
process.exit(1);
|
|
2562
|
+
}
|
|
2563
|
+
}
|
|
2527
2564
|
if (process.platform !== 'darwin') {
|
|
2528
2565
|
// No broker + no biometry prompt off darwin: secrets already resolve
|
|
2529
2566
|
// durably from the OS store (libsecret / Credential Manager) on every
|
|
@@ -2537,15 +2574,15 @@ Examples:
|
|
|
2537
2574
|
console.error(chalk.red('Specify one or more bundle names, or --all.'));
|
|
2538
2575
|
process.exit(1);
|
|
2539
2576
|
}
|
|
2540
|
-
let ttlMs
|
|
2541
|
-
|
|
2542
|
-
|
|
2543
|
-
|
|
2544
|
-
|
|
2545
|
-
|
|
2546
|
-
|
|
2547
|
-
ttlMs = secs * 1000;
|
|
2577
|
+
let ttlMs;
|
|
2578
|
+
try {
|
|
2579
|
+
ttlMs = resolveUnlockTtlMs(opts.ttl, opts.until);
|
|
2580
|
+
}
|
|
2581
|
+
catch (err) {
|
|
2582
|
+
console.error(chalk.red(err.message));
|
|
2583
|
+
process.exit(1);
|
|
2548
2584
|
}
|
|
2585
|
+
const expiresAt = Date.now() + ttlMs;
|
|
2549
2586
|
if (!(await ensureAgentRunning())) {
|
|
2550
2587
|
console.error(chalk.red('Could not start the secrets broker.'));
|
|
2551
2588
|
process.exit(1);
|
|
@@ -2571,7 +2608,7 @@ Examples:
|
|
|
2571
2608
|
noAgent: true,
|
|
2572
2609
|
caller: 'unlock secrets',
|
|
2573
2610
|
agent: harness,
|
|
2574
|
-
duration: humanRemaining(
|
|
2611
|
+
duration: humanRemaining(expiresAt),
|
|
2575
2612
|
keyMode: 'storage',
|
|
2576
2613
|
});
|
|
2577
2614
|
// Migrations are authorized only by this explicit unlock. The bundle
|
|
@@ -2587,7 +2624,7 @@ Examples:
|
|
|
2587
2624
|
saveSession(name, {
|
|
2588
2625
|
bundle,
|
|
2589
2626
|
env,
|
|
2590
|
-
expiresAt
|
|
2627
|
+
expiresAt,
|
|
2591
2628
|
sleepPersist: durable,
|
|
2592
2629
|
harness,
|
|
2593
2630
|
});
|
|
@@ -2606,7 +2643,7 @@ Examples:
|
|
|
2606
2643
|
agent: harness,
|
|
2607
2644
|
ttlMs,
|
|
2608
2645
|
});
|
|
2609
|
-
console.log(`${chalk.green('unlocked')} ${chalk.cyan(name)} ${chalk.gray(`(${Object.keys(env).length} keys, ${humanRemaining(
|
|
2646
|
+
console.log(`${chalk.green('unlocked')} ${chalk.cyan(name)} ${chalk.gray(`(${Object.keys(env).length} keys, ${humanRemaining(expiresAt)})`)}`);
|
|
2610
2647
|
}
|
|
2611
2648
|
else {
|
|
2612
2649
|
console.error(chalk.red(`Failed to load '${name}' into the agent.`));
|
|
@@ -11,6 +11,7 @@
|
|
|
11
11
|
*/
|
|
12
12
|
import { type SessionMeta } from '../lib/session/types.js';
|
|
13
13
|
import type { ActiveSession } from '../lib/session/active.js';
|
|
14
|
+
import { type LiveStatusFilter } from './sessions.js';
|
|
14
15
|
/**
|
|
15
16
|
* The single canonical filter state. Every field has a flag equivalent, so the
|
|
16
17
|
* same view is reachable interactively (hotkeys) or from the command line (flags).
|
|
@@ -32,6 +33,23 @@ export interface BrowserFilter {
|
|
|
32
33
|
projectScope: 'repo' | 'all';
|
|
33
34
|
/** time window (undefined = all time) — the `W` key / `--since`. */
|
|
34
35
|
window?: string;
|
|
36
|
+
/** individual live states; an OR-union and, like the CLI flags, running-only. */
|
|
37
|
+
statuses: LiveStatusFilter[];
|
|
38
|
+
/** named project scope, independent of the repo/all hotkey. */
|
|
39
|
+
project?: string;
|
|
40
|
+
/** upper time bound from --until. */
|
|
41
|
+
until?: string;
|
|
42
|
+
/** retain only routine-origin sessions. */
|
|
43
|
+
routine: boolean;
|
|
44
|
+
/** indexed resource filters shared by the flag and focus surfaces. */
|
|
45
|
+
skill?: string;
|
|
46
|
+
plugin?: string;
|
|
47
|
+
/** discovery cap. */
|
|
48
|
+
limit: number;
|
|
49
|
+
/** include unmanaged native-home transcripts. */
|
|
50
|
+
unmanaged: boolean;
|
|
51
|
+
/** candidate order shared with --sort. */
|
|
52
|
+
sort: 'timestamp' | 'cost' | 'duration';
|
|
35
53
|
}
|
|
36
54
|
/**
|
|
37
55
|
* Complete a seed into the filter the picker actually runs on.
|
|
@@ -97,6 +115,9 @@ export declare function bareBrowserSeed(opts: {
|
|
|
97
115
|
inTeam?: string;
|
|
98
116
|
favorites?: boolean;
|
|
99
117
|
}): Partial<BrowserFilter>;
|
|
118
|
+
/** Build the static peer query. Keeping it pure pins the per-device alias
|
|
119
|
+
* contract: latest/oldest travel unresolved and are resolved by each peer. */
|
|
120
|
+
export declare function remotePoolArgs(f: BrowserFilter, fixedFilters: boolean): string[];
|
|
100
121
|
/**
|
|
101
122
|
* A live session's stable row key: its session id when the agent reported one,
|
|
102
123
|
* else a per-machine handle so an id-less live session (a just-booted harness, a
|
|
@@ -126,7 +147,7 @@ export declare function liveSessionToMeta(a: ActiveSession, self: string): Sessi
|
|
|
126
147
|
* `--active --json` listed it. Live sessions the pool lacks are appended as their
|
|
127
148
|
* own rows, then the whole set is grouped local-machine-first.
|
|
128
149
|
*/
|
|
129
|
-
export declare function mergeLiveIntoPool(rows: SessionMeta[], live: Map<string, ActiveSession>, self: string): SessionMeta[];
|
|
150
|
+
export declare function mergeLiveIntoPool(rows: SessionMeta[], live: Map<string, ActiveSession>, self: string, includeUnindexed?: boolean): SessionMeta[];
|
|
130
151
|
/**
|
|
131
152
|
* Whether to render the host-program column. It belongs to the running view
|
|
132
153
|
* only, so this gates on the FILTER — not on the live index being populated.
|
|
@@ -135,6 +156,20 @@ export declare function mergeLiveIntoPool(rows: SessionMeta[], live: Map<string,
|
|
|
135
156
|
* listing that has no live rows to explain it.
|
|
136
157
|
*/
|
|
137
158
|
export declare function shouldShowHostColumn(f: BrowserFilter, live: Map<string, ActiveSession> | null, rows: SessionMeta[]): boolean;
|
|
159
|
+
/** Apply the cheap in-memory filters (agent / device / project / running / favorites). */
|
|
160
|
+
export declare function applyFilters(rows: SessionMeta[], live: Map<string, ActiveSession>, f: BrowserFilter, self: string, favorites: Set<string>): SessionMeta[];
|
|
161
|
+
/** One-shot form of the browser's canonical candidate pipeline. Focus uses this
|
|
162
|
+
* instead of maintaining a second discovery/filter implementation. */
|
|
163
|
+
export declare function collectSessionCandidates(initial: Partial<BrowserFilter>, opts?: {
|
|
164
|
+
local?: boolean;
|
|
165
|
+
hosts?: string[];
|
|
166
|
+
includeLive?: boolean;
|
|
167
|
+
}): Promise<{
|
|
168
|
+
sessions: SessionMeta[];
|
|
169
|
+
liveById: Map<string, ActiveSession>;
|
|
170
|
+
self: string;
|
|
171
|
+
unreachable: string[];
|
|
172
|
+
}>;
|
|
138
173
|
/**
|
|
139
174
|
* Launch the interactive session browser. `initial` seeds the filter (e.g.
|
|
140
175
|
* `{ running: true }` for `--active`). Resolves after the user resumes a session
|
|
@@ -16,11 +16,13 @@ import { dynamicPicker } from '../lib/picker.js';
|
|
|
16
16
|
import { isSessionTrackedAgent } from '../lib/session/types.js';
|
|
17
17
|
import { discoverSessions } from '../lib/session/discover.js';
|
|
18
18
|
import { gatherRemoteList } from '../lib/session/remote-list.js';
|
|
19
|
+
import { resolveVersionAliasLoose } from '../lib/versions.js';
|
|
20
|
+
import { AGENTS } from '../lib/agents.js';
|
|
19
21
|
import { enrichTeamOrigins, safeTeamText } from '../lib/session/team-filter.js';
|
|
20
22
|
import { listFavorites, toggleFavorite } from '../lib/session/favorites.js';
|
|
21
23
|
import { machineId, normalizeHost } from '../lib/session/sync/config.js';
|
|
22
24
|
import { buildPreview } from './sessions-picker.js';
|
|
23
|
-
import { formatPickerLabel, pickerColumnsFor, ticketLabel, mergeLocalFirst, gatherActiveSessions, liveHostLabel, LIVE_ROW_PREFIX, cleanPreview, handlePickedSession, shouldIncludeLocal, remoteHostsToDial, matchesTeam, formatLiveStatusHeadline, } from './sessions.js';
|
|
25
|
+
import { formatPickerLabel, pickerColumnsFor, ticketLabel, mergeLocalFirst, gatherActiveSessions, liveHostLabel, LIVE_ROW_PREFIX, cleanPreview, handlePickedSession, shouldIncludeLocal, remoteHostsToDial, matchesTeam, formatLiveStatusHeadline, isRunningLiveSession, matchesLiveStatus, parseAgentFilter, } from './sessions.js';
|
|
24
26
|
/**
|
|
25
27
|
* Complete a seed into the filter the picker actually runs on.
|
|
26
28
|
*
|
|
@@ -44,6 +46,15 @@ export function buildInitialFilter(initial) {
|
|
|
44
46
|
team: initial.team,
|
|
45
47
|
projectScope: initial.projectScope ?? 'repo',
|
|
46
48
|
window: 'window' in initial ? initial.window : '30d',
|
|
49
|
+
statuses: initial.statuses ?? [],
|
|
50
|
+
project: initial.project,
|
|
51
|
+
until: initial.until,
|
|
52
|
+
routine: initial.routine ?? false,
|
|
53
|
+
skill: initial.skill,
|
|
54
|
+
plugin: initial.plugin,
|
|
55
|
+
limit: initial.limit ?? 500,
|
|
56
|
+
unmanaged: initial.unmanaged ?? false,
|
|
57
|
+
sort: initial.sort ?? 'timestamp',
|
|
47
58
|
};
|
|
48
59
|
}
|
|
49
60
|
/** Cache key for the transcript pool: every filter that changes what is FETCHED
|
|
@@ -54,10 +65,16 @@ function poolCacheKey(f) {
|
|
|
54
65
|
// fetches the same deep pool and is then narrowed in memory. Keying on the name
|
|
55
66
|
// would make `t` the one hotkey that re-fans-out the fleet on every step of the
|
|
56
67
|
// cycle, at up to REMOTE_TIMEOUT_MS per unreachable peer.
|
|
57
|
-
|
|
68
|
+
const versionScoped = f.agent?.includes('@') ? f.agent : '';
|
|
69
|
+
return [
|
|
70
|
+
f.window ?? 'all', f.until ?? '', f.teams, f.team ? 'team' : '', versionScoped,
|
|
71
|
+
f.project ?? '', f.routine, f.skill ?? '', f.plugin ?? '', f.limit, f.unmanaged, f.sort,
|
|
72
|
+
].join('|');
|
|
58
73
|
}
|
|
59
74
|
/** Pool size when a team filter is active; one team's rows can sit anywhere. */
|
|
60
75
|
const WHOLE_TEAM_POOL_LIMIT = 5000;
|
|
76
|
+
/** A cold peer may need to resolve an installed-version alias and scan its index. */
|
|
77
|
+
const BROWSER_PEER_TIMEOUT_MS = 30_000;
|
|
61
78
|
/** Ordered window cycle for the `W` key. `undefined` = all time. */
|
|
62
79
|
const WINDOW_CYCLE = [undefined, '1d', '7d', '30d'];
|
|
63
80
|
/** Return the next value in `[undefined, ...options]`, wrapping. */
|
|
@@ -105,8 +122,10 @@ export function sessionMatchesQuery(s, query) {
|
|
|
105
122
|
*/
|
|
106
123
|
export function browserFilterToArgv(f, query = '') {
|
|
107
124
|
const a = ['sessions'];
|
|
108
|
-
if (f.running)
|
|
125
|
+
if (f.statuses.length === 0 && f.running)
|
|
109
126
|
a.push('--active');
|
|
127
|
+
for (const status of f.statuses)
|
|
128
|
+
a.push(`--${status === 'orphaned' ? 'orphan' : status}`);
|
|
110
129
|
if (f.teams)
|
|
111
130
|
a.push('--teams');
|
|
112
131
|
if (f.favorites)
|
|
@@ -121,6 +140,22 @@ export function browserFilterToArgv(f, query = '') {
|
|
|
121
140
|
a.push('--all');
|
|
122
141
|
if (f.window)
|
|
123
142
|
a.push('--since', f.window);
|
|
143
|
+
if (f.until)
|
|
144
|
+
a.push('--until', f.until);
|
|
145
|
+
if (f.project)
|
|
146
|
+
a.push('--project', f.project);
|
|
147
|
+
if (f.routine)
|
|
148
|
+
a.push('--routine');
|
|
149
|
+
if (f.skill)
|
|
150
|
+
a.push('--skill', f.skill);
|
|
151
|
+
if (f.plugin)
|
|
152
|
+
a.push('--plugin', f.plugin);
|
|
153
|
+
if (f.unmanaged)
|
|
154
|
+
a.push('--unmanaged');
|
|
155
|
+
if (f.sort !== 'timestamp')
|
|
156
|
+
a.push('--sort', f.sort === 'cost' ? 'cost' : 'duration');
|
|
157
|
+
if (f.limit !== 500)
|
|
158
|
+
a.push('--limit', String(f.limit));
|
|
124
159
|
const q = query.trim();
|
|
125
160
|
if (q)
|
|
126
161
|
a.push(JSON.stringify(q));
|
|
@@ -151,6 +186,7 @@ export function activeBrowserSeed(opts) {
|
|
|
151
186
|
// --all widens the window to all-time (project is already 'all' here);
|
|
152
187
|
// --since still overrides.
|
|
153
188
|
window: opts.since ?? (opts.all ? undefined : '30d'),
|
|
189
|
+
statuses: [],
|
|
154
190
|
};
|
|
155
191
|
}
|
|
156
192
|
/**
|
|
@@ -184,6 +220,7 @@ export function bareBrowserSeed(opts) {
|
|
|
184
220
|
// --all maxes every non-status filter: all dirs AND all-time. --since wins.
|
|
185
221
|
projectScope: opts.all || scoped || wholeTeam ? 'all' : 'repo',
|
|
186
222
|
window: opts.since ?? (opts.all || wholeTeam ? undefined : '30d'),
|
|
223
|
+
statuses: [],
|
|
187
224
|
};
|
|
188
225
|
}
|
|
189
226
|
/** Copy text to the OS clipboard (best-effort; silently no-ops if unavailable). */
|
|
@@ -214,8 +251,13 @@ function copyToClipboard(text) {
|
|
|
214
251
|
* `hosts` is the explicit `--host`/`--device` scope (if any): it restricts which
|
|
215
252
|
* peers are dialed and whether local is included, honoring the flag's "scope,
|
|
216
253
|
* not add" contract instead of always sweeping the whole fleet. */
|
|
217
|
-
async function fetchRawPool(f, self, local, hosts) {
|
|
254
|
+
async function fetchRawPool(f, self, local, hosts, fixedFilters = false) {
|
|
218
255
|
const since = f.window;
|
|
256
|
+
const selectedAgent = f.agent ? parseAgentFilter(f.agent) : {};
|
|
257
|
+
const parsedAgent = fixedFilters ? selectedAgent : {};
|
|
258
|
+
const localAgentVersion = parsedAgent.agent && parsedAgent.agent in AGENTS
|
|
259
|
+
? resolveVersionAliasLoose(parsedAgent.agent, parsedAgent.version)
|
|
260
|
+
: parsedAgent.version;
|
|
219
261
|
let unreachable = [];
|
|
220
262
|
// Local pool: wide (every directory) — device/agent/project are applied in
|
|
221
263
|
// memory so a hotkey toggle is instant and doesn't re-hit the disk. Skipped
|
|
@@ -223,16 +265,33 @@ async function fetchRawPool(f, self, local, hosts) {
|
|
|
223
265
|
let rows = shouldIncludeLocal(hosts, self)
|
|
224
266
|
? await discoverSessions({
|
|
225
267
|
all: true,
|
|
268
|
+
agent: parsedAgent.agent,
|
|
269
|
+
version: localAgentVersion,
|
|
270
|
+
includeUnmanaged: f.unmanaged,
|
|
226
271
|
cwd: process.cwd(),
|
|
227
272
|
since,
|
|
273
|
+
until: f.until,
|
|
274
|
+
project: f.project,
|
|
275
|
+
origin: f.routine ? 'routine' : undefined,
|
|
276
|
+
skill: f.skill,
|
|
277
|
+
plugin: f.plugin,
|
|
228
278
|
excludeTeamOrigin: !f.teams,
|
|
229
279
|
// A team filter reaches back past the usual browse window, so the pool it
|
|
230
280
|
// draws from has to as well — otherwise the newest 500 rows decide which
|
|
231
281
|
// teams exist.
|
|
232
|
-
limit: f.team ? WHOLE_TEAM_POOL_LIMIT :
|
|
233
|
-
sortBy:
|
|
282
|
+
limit: f.team ? WHOLE_TEAM_POOL_LIMIT : f.limit,
|
|
283
|
+
sortBy: f.sort,
|
|
234
284
|
})
|
|
235
285
|
: [];
|
|
286
|
+
// A synced mirror knows which VERSION produced a session, but it cannot know
|
|
287
|
+
// which version is currently installed as latest/oldest on the origin device.
|
|
288
|
+
// For an alias selector, keep only this device's rows here and accept peer
|
|
289
|
+
// rows only from the live query below, where that peer resolves its own
|
|
290
|
+
// inventory. If the peer is unavailable, an empty/partial result is honest;
|
|
291
|
+
// widening to cached sessions from every version is not.
|
|
292
|
+
if (selectedAgent.version === 'latest' || selectedAgent.version === 'oldest') {
|
|
293
|
+
rows = rows.filter((row) => (row.machine ?? self) === self);
|
|
294
|
+
}
|
|
236
295
|
// Fleet: fold in peers' own indexes over SSH (no sync), same as the flag path.
|
|
237
296
|
// Skipped under --local. An explicit --host/--device scopes exactly which peers
|
|
238
297
|
// are dialed (undefined = sweep every online device). Best-effort — a fan-out
|
|
@@ -251,12 +310,10 @@ async function fetchRawPool(f, self, local, hosts) {
|
|
|
251
310
|
// the same bug one hop out. A numeric --limit is forwarded rather than
|
|
252
311
|
// --in-team itself, which a peer on an older build would reject as an
|
|
253
312
|
// unknown option and fail the whole fan-out.
|
|
254
|
-
const forwarded =
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
forwarded.push('--teams');
|
|
259
|
-
const remoteResult = await gatherRemoteList(forwarded, remoteHosts);
|
|
313
|
+
const forwarded = remotePoolArgs(f, fixedFilters);
|
|
314
|
+
const remoteResult = await gatherRemoteList(forwarded, remoteHosts, {
|
|
315
|
+
timeoutMs: BROWSER_PEER_TIMEOUT_MS,
|
|
316
|
+
});
|
|
260
317
|
unreachable = remoteResult.unreachable;
|
|
261
318
|
if (remoteResult.sessions.length > 0)
|
|
262
319
|
rows = mergeLocalFirst([...rows, ...remoteResult.sessions], self);
|
|
@@ -272,6 +329,32 @@ async function fetchRawPool(f, self, local, hosts) {
|
|
|
272
329
|
rows = enrichTeamOrigins(rows);
|
|
273
330
|
return { key: poolCacheKey(f), rows, unreachable };
|
|
274
331
|
}
|
|
332
|
+
/** Build the static peer query. Keeping it pure pins the per-device alias
|
|
333
|
+
* contract: latest/oldest travel unresolved and are resolved by each peer. */
|
|
334
|
+
export function remotePoolArgs(f, fixedFilters) {
|
|
335
|
+
const forwarded = ['sessions', '--all', '--json', '--limit', String(f.team ? WHOLE_TEAM_POOL_LIMIT : f.limit)];
|
|
336
|
+
if (f.window)
|
|
337
|
+
forwarded.push('--since', f.window);
|
|
338
|
+
if (f.until)
|
|
339
|
+
forwarded.push('--until', f.until);
|
|
340
|
+
if (f.project)
|
|
341
|
+
forwarded.push('--project', f.project);
|
|
342
|
+
if (f.routine)
|
|
343
|
+
forwarded.push('--routine');
|
|
344
|
+
if (f.skill)
|
|
345
|
+
forwarded.push('--skill', f.skill);
|
|
346
|
+
if (f.plugin)
|
|
347
|
+
forwarded.push('--plugin', f.plugin);
|
|
348
|
+
if (f.unmanaged)
|
|
349
|
+
forwarded.push('--unmanaged');
|
|
350
|
+
if (f.sort !== 'timestamp')
|
|
351
|
+
forwarded.push('--sort', f.sort === 'cost' ? 'cost' : 'duration');
|
|
352
|
+
if (f.agent && (fixedFilters || f.agent.includes('@')))
|
|
353
|
+
forwarded.push('--agent', f.agent);
|
|
354
|
+
if (f.teams)
|
|
355
|
+
forwarded.push('--teams');
|
|
356
|
+
return forwarded;
|
|
357
|
+
}
|
|
275
358
|
/**
|
|
276
359
|
* A live session's stable row key: its session id when the agent reported one,
|
|
277
360
|
* else a per-machine handle so an id-less live session (a just-booted harness, a
|
|
@@ -344,7 +427,12 @@ export function liveSessionToMeta(a, self) {
|
|
|
344
427
|
* `--active --json` listed it. Live sessions the pool lacks are appended as their
|
|
345
428
|
* own rows, then the whole set is grouped local-machine-first.
|
|
346
429
|
*/
|
|
347
|
-
export function mergeLiveIntoPool(rows, live, self) {
|
|
430
|
+
export function mergeLiveIntoPool(rows, live, self, includeUnindexed = true) {
|
|
431
|
+
// A latest/oldest selector is resolved by the peer's transcript query. A live
|
|
432
|
+
// row absent from that result has no proof it belongs to the resolved version,
|
|
433
|
+
// so callers using an alias fail closed instead of widening the selector.
|
|
434
|
+
if (!includeUnindexed)
|
|
435
|
+
return rows;
|
|
348
436
|
const known = new Set(rows.map((r) => r.id));
|
|
349
437
|
const extra = [];
|
|
350
438
|
for (const [key, a] of live) {
|
|
@@ -366,27 +454,82 @@ export function shouldShowHostColumn(f, live, rows) {
|
|
|
366
454
|
return rows.some((r) => liveHostLabel(live.get(r.id)) !== '');
|
|
367
455
|
}
|
|
368
456
|
/** Apply the cheap in-memory filters (agent / device / project / running / favorites). */
|
|
369
|
-
function applyFilters(rows, live, f, self, favorites) {
|
|
457
|
+
export function applyFilters(rows, live, f, self, favorites) {
|
|
370
458
|
let out = rows;
|
|
371
459
|
// A projected live row is keyed by pid/task when it has no session id, and a
|
|
372
460
|
// favorite is always keyed by a real session id — so an id-less row can never
|
|
373
461
|
// be favorited and correctly drops out here.
|
|
374
462
|
if (f.favorites)
|
|
375
463
|
out = out.filter((r) => favorites.has(r.id));
|
|
376
|
-
if (f.agent)
|
|
377
|
-
|
|
464
|
+
if (f.agent) {
|
|
465
|
+
const { agent, version: rawVersion } = parseAgentFilter(f.agent);
|
|
466
|
+
const localVersion = agent && agent in AGENTS
|
|
467
|
+
? resolveVersionAliasLoose(agent, rawVersion)
|
|
468
|
+
: rawVersion;
|
|
469
|
+
const peerResolvedAlias = rawVersion === 'latest' || rawVersion === 'oldest';
|
|
470
|
+
out = out.filter((r) => {
|
|
471
|
+
if (r.agent !== agent)
|
|
472
|
+
return false;
|
|
473
|
+
// Only a LIVE peer result may claim it resolved latest/oldest against that
|
|
474
|
+
// device's installed inventory. A synced mirror is historical data, not a
|
|
475
|
+
// version-inventory oracle.
|
|
476
|
+
if (peerResolvedAlias && (r.machine ?? self) !== self)
|
|
477
|
+
return r._remote === true;
|
|
478
|
+
if (!localVersion)
|
|
479
|
+
return true;
|
|
480
|
+
return r.version === localVersion;
|
|
481
|
+
});
|
|
482
|
+
}
|
|
378
483
|
if (f.device)
|
|
379
484
|
out = out.filter((r) => (r.machine ?? self) === f.device);
|
|
380
485
|
if (f.team)
|
|
381
486
|
out = out.filter((r) => matchesTeam(r, f.team));
|
|
487
|
+
if (f.project) {
|
|
488
|
+
const q = f.project.toLowerCase();
|
|
489
|
+
out = out.filter((r) => (r.project ?? '').toLowerCase().includes(q) || (r.cwd ?? '').toLowerCase().includes(q));
|
|
490
|
+
}
|
|
382
491
|
if (f.projectScope === 'repo') {
|
|
383
492
|
const cwd = process.cwd();
|
|
384
493
|
out = out.filter((r) => !!r.cwd && (r.cwd === cwd || r.cwd.startsWith(cwd + '/')));
|
|
385
494
|
}
|
|
386
|
-
|
|
387
|
-
|
|
495
|
+
// The live registry retains dead rows so explicit --closed/--crashed filters
|
|
496
|
+
// can recover them. Bare --active means currently active; a lifecycle filter
|
|
497
|
+
// intentionally replaces that default predicate with its exact status union.
|
|
498
|
+
if (f.running && f.statuses.length === 0) {
|
|
499
|
+
out = out.filter((r) => {
|
|
500
|
+
const active = live.get(r.id);
|
|
501
|
+
return !!active && isRunningLiveSession(active);
|
|
502
|
+
});
|
|
503
|
+
}
|
|
504
|
+
if (f.statuses.length > 0) {
|
|
505
|
+
out = out.filter((r) => {
|
|
506
|
+
const active = live.get(r.id);
|
|
507
|
+
return !!active && f.statuses.some((status) => matchesLiveStatus(active, status));
|
|
508
|
+
});
|
|
509
|
+
}
|
|
388
510
|
return out;
|
|
389
511
|
}
|
|
512
|
+
/** One-shot form of the browser's canonical candidate pipeline. Focus uses this
|
|
513
|
+
* instead of maintaining a second discovery/filter implementation. */
|
|
514
|
+
export async function collectSessionCandidates(initial, opts = {}) {
|
|
515
|
+
const self = machineId();
|
|
516
|
+
const filter = buildInitialFilter(initial);
|
|
517
|
+
if (filter.statuses.length > 0)
|
|
518
|
+
filter.running = true;
|
|
519
|
+
const hosts = opts.hosts && opts.hosts.length > 0 ? opts.hosts : undefined;
|
|
520
|
+
const pool = await fetchRawPool(filter, self, opts.local ?? false, hosts, true);
|
|
521
|
+
let liveById = new Map();
|
|
522
|
+
if (filter.running || opts.includeLive) {
|
|
523
|
+
const { sessions } = await gatherActiveSessions({ local: opts.local ?? false, hosts });
|
|
524
|
+
liveById = indexLiveRows(sessions, self);
|
|
525
|
+
}
|
|
526
|
+
const includeUnindexedLive = !filter.agent?.match(/@(latest|oldest)$/);
|
|
527
|
+
const rows = filter.running || opts.includeLive
|
|
528
|
+
? mergeLiveIntoPool(pool.rows, liveById, self, includeUnindexedLive)
|
|
529
|
+
: pool.rows;
|
|
530
|
+
const sessions = applyFilters(rows, liveById, filter, self, listFavorites());
|
|
531
|
+
return { sessions, liveById, self, unreachable: pool.unreachable };
|
|
532
|
+
}
|
|
390
533
|
/** Derive the SSH-launch origin tag for a picker row from the live index. Set
|
|
391
534
|
* only when the live session's provenance is ssh transport; `device` is the
|
|
392
535
|
* resolved origin device (absent → the row shows a bare `ssh`). Rows without a
|
|
@@ -492,7 +635,10 @@ export async function runSessionBrowser(initial = {}, opts = {}) {
|
|
|
492
635
|
liveCache = live;
|
|
493
636
|
// Live sessions the transcript pool lacks become rows of their own, so the
|
|
494
637
|
// running view lists every active session, not just the ones already indexed.
|
|
495
|
-
const
|
|
638
|
+
const includeUnindexedLive = !f.agent?.match(/@(latest|oldest)$/);
|
|
639
|
+
const rows = f.running && live
|
|
640
|
+
? mergeLiveIntoPool(pool.rows, live, self, includeUnindexedLive)
|
|
641
|
+
: pool.rows;
|
|
496
642
|
agentsInPool = distinct(rows.map((r) => r.agent));
|
|
497
643
|
devicesInPool = distinct(rows.map((r) => r.machine ?? self));
|
|
498
644
|
// Both ends of the lineage seed the cycle: teams a row spawned, and teams a
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { Command } from 'commander';
|
|
2
|
+
import type { SessionMeta } from '../lib/session/types.js';
|
|
2
3
|
import { type Backend, type EngineContext, type Packing } from '../lib/terminal/index.js';
|
|
3
4
|
/** Opening more than this many live sessions at once asks for confirmation first. */
|
|
4
5
|
export declare const CONFIRM_THRESHOLD = 5;
|
|
@@ -30,6 +31,7 @@ export declare function resumeSelectorInPlace(selector: string): Promise<void>;
|
|
|
30
31
|
export declare function dispatchSessionLifecycleInPlace(selector: string, hosts?: string[]): Promise<void>;
|
|
31
32
|
export declare function buildSessionLifecycleArgs(selector: string, hosts?: string[]): string[];
|
|
32
33
|
export declare function resolveResumePacking(options: Pick<ResumeOptions, 'splits'>): Packing;
|
|
34
|
+
export declare function resumeHostMismatch(session: Pick<SessionMeta, 'shortId' | 'machine'>, requestedHost: string, self?: string): string | null;
|
|
33
35
|
/**
|
|
34
36
|
* Decide which backend to launch into. Returns a concrete backend, `'inplace'`
|
|
35
37
|
* (resume in the current process — no GUI/tmux available), or `'cancel'` (the
|