@phnx-labs/agents-cli 1.20.90 → 1.20.92
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 +276 -0
- package/README.md +1 -1
- package/dist/bin/agents +0 -0
- package/dist/commands/activity.d.ts +72 -6
- package/dist/commands/activity.js +198 -49
- package/dist/commands/beta.js +1 -0
- package/dist/commands/doctor.js +4 -2
- package/dist/commands/exec.d.ts +14 -0
- package/dist/commands/exec.js +144 -14
- package/dist/commands/feed.js +77 -4
- package/dist/commands/hooks.js +22 -6
- package/dist/commands/perf.d.ts +14 -0
- package/dist/commands/perf.js +221 -0
- package/dist/commands/projects.d.ts +12 -0
- package/dist/commands/projects.js +358 -0
- package/dist/commands/routines.js +30 -24
- package/dist/commands/secrets.d.ts +43 -4
- package/dist/commands/secrets.js +217 -32
- package/dist/commands/send.d.ts +5 -1
- package/dist/commands/send.js +1 -1
- package/dist/commands/sessions-picker.d.ts +15 -0
- package/dist/commands/sessions-picker.js +107 -13
- package/dist/commands/sessions-resume.d.ts +2 -0
- package/dist/commands/sessions-resume.js +9 -1
- package/dist/commands/sessions.d.ts +10 -5
- package/dist/commands/sessions.js +65 -27
- package/dist/index.js +19 -3
- package/dist/lib/activity.d.ts +80 -13
- package/dist/lib/activity.js +418 -74
- package/dist/lib/beta.d.ts +1 -1
- package/dist/lib/beta.js +1 -1
- package/dist/lib/catchup.d.ts +105 -0
- package/dist/lib/catchup.js +160 -0
- package/dist/lib/channels/providers/desktop.d.ts +49 -0
- package/dist/lib/channels/providers/desktop.js +132 -0
- package/dist/lib/channels/providers/index.js +2 -0
- package/dist/lib/daemon.js +74 -13
- package/dist/lib/devices/registry.d.ts +14 -0
- package/dist/lib/devices/registry.js +37 -0
- package/dist/lib/events.d.ts +12 -0
- package/dist/lib/events.js +122 -9
- package/dist/lib/exec.js +10 -0
- package/dist/lib/feed-broadcast.d.ts +47 -0
- package/dist/lib/feed-broadcast.js +65 -1
- package/dist/lib/feed-post.d.ts +10 -0
- package/dist/lib/feed-post.js +9 -3
- package/dist/lib/feed.d.ts +47 -1
- package/dist/lib/feed.js +38 -0
- package/dist/lib/hooks/cache.d.ts +2 -0
- package/dist/lib/hooks/cache.js +24 -4
- package/dist/lib/hosts/remote-cmd.js +4 -0
- package/dist/lib/menubar/MenubarHelper.app/Contents/MacOS/MenubarHelper +0 -0
- package/dist/lib/menubar/install-menubar.d.ts +14 -4
- package/dist/lib/menubar/install-menubar.js +20 -6
- package/dist/lib/overdue.d.ts +14 -0
- package/dist/lib/overdue.js +37 -1
- package/dist/lib/perf/db.d.ts +25 -0
- package/dist/lib/perf/db.js +290 -0
- package/dist/lib/perf/spool.d.ts +18 -0
- package/dist/lib/perf/spool.js +79 -0
- package/dist/lib/perf/types.d.ts +45 -0
- package/dist/lib/perf/types.js +2 -0
- package/dist/lib/project-key.d.ts +44 -0
- package/dist/lib/project-key.js +79 -0
- package/dist/lib/project-root.js +16 -0
- package/dist/lib/project-status.d.ts +69 -0
- package/dist/lib/project-status.js +101 -0
- package/dist/lib/projects.d.ts +138 -0
- package/dist/lib/projects.js +301 -0
- package/dist/lib/remote-agents-json.d.ts +9 -0
- package/dist/lib/remote-agents-json.js +11 -5
- package/dist/lib/routines-project.js +6 -0
- package/dist/lib/routines.d.ts +30 -1
- package/dist/lib/routines.js +11 -0
- package/dist/lib/secrets/Agents CLI.app/Contents/CodeResources +0 -0
- package/dist/lib/secrets/Agents CLI.app/Contents/MacOS/Agents CLI +0 -0
- package/dist/lib/secrets/list-filter.d.ts +94 -0
- package/dist/lib/secrets/list-filter.js +245 -0
- package/dist/lib/session/bash-command.d.ts +53 -0
- package/dist/lib/session/bash-command.js +364 -0
- package/dist/lib/session/digest.d.ts +13 -0
- package/dist/lib/session/digest.js +48 -1
- package/dist/lib/session/discover.d.ts +1 -2
- package/dist/lib/session/discover.js +7 -24
- package/dist/lib/session/highlights.d.ts +82 -0
- package/dist/lib/session/highlights.js +251 -0
- package/dist/lib/session/parse.js +23 -1
- package/dist/lib/session/relative-time.d.ts +37 -0
- package/dist/lib/session/relative-time.js +96 -8
- package/dist/lib/session/remote-list.js +5 -2
- package/dist/lib/session/render.d.ts +9 -9
- package/dist/lib/session/render.js +112 -73
- package/dist/lib/session/types.d.ts +4 -1
- package/dist/lib/ssh-exec.d.ts +6 -0
- package/dist/lib/ssh-exec.js +10 -1
- package/dist/lib/startup/command-registry.d.ts +2 -0
- package/dist/lib/startup/command-registry.js +4 -0
- package/dist/lib/state.d.ts +11 -0
- package/dist/lib/state.js +16 -0
- package/dist/lib/terminal/backends/index.d.ts +10 -2
- package/dist/lib/terminal/backends/index.js +14 -2
- package/dist/lib/terminal/backends/terminal-app.d.ts +13 -0
- package/dist/lib/terminal/backends/terminal-app.js +73 -0
- package/dist/lib/terminal/index.d.ts +2 -1
- package/dist/lib/terminal/index.js +2 -1
- package/dist/lib/terminal/preferred.d.ts +89 -0
- package/dist/lib/terminal/preferred.js +87 -0
- package/dist/lib/terminal/run-surface.d.ts +82 -0
- package/dist/lib/terminal/run-surface.js +146 -0
- package/dist/lib/terminal/types.d.ts +1 -1
- package/dist/lib/types.d.ts +1 -1
- package/package.json +4 -1
package/dist/lib/state.js
CHANGED
|
@@ -88,6 +88,9 @@ const WEBHOOKS_DIR = path.join(USER_AGENTS_DIR, 'webhooks');
|
|
|
88
88
|
// monitor is a routine whose trigger is a watched source instead of a clock.
|
|
89
89
|
const MONITORS_DIR = path.join(USER_AGENTS_DIR, 'monitors');
|
|
90
90
|
const TEAMS_DIR = path.join(USER_AGENTS_DIR, 'teams');
|
|
91
|
+
// Named project definitions (the layer above the --project convention). Sibling
|
|
92
|
+
// of ROUTINES_DIR/TEAMS_DIR: hand-editable YAML, synced across machines by push/pull.
|
|
93
|
+
const PROJECTS_DIR = path.join(USER_AGENTS_DIR, 'projects');
|
|
91
94
|
// History bucket (durable).
|
|
92
95
|
const SESSIONS_DIR = path.join(HISTORY_DIR, 'sessions');
|
|
93
96
|
const SESSIONS_DB_PATH = path.join(SESSIONS_DIR, 'sessions.db');
|
|
@@ -116,6 +119,8 @@ const CLOUD_DIR = path.join(CACHE_DIR, 'cloud');
|
|
|
116
119
|
const DRIVE_DIR = path.join(CACHE_DIR, 'drive');
|
|
117
120
|
const TERMINALS_DIR = path.join(CACHE_DIR, 'terminals');
|
|
118
121
|
const LOGS_DIR = path.join(CACHE_DIR, 'logs');
|
|
122
|
+
/** Disposable performance samples (~/.agents/.cache/perf/) — safe to wipe. */
|
|
123
|
+
const PERF_DIR = path.join(CACHE_DIR, 'perf');
|
|
119
124
|
const RUNTIME_STATE_DIR = path.join(CACHE_DIR, 'state');
|
|
120
125
|
const COMPANION_CACHE_DIR = path.join(CACHE_DIR, 'companion');
|
|
121
126
|
const BROWSER_RUNTIME_DIR = path.join(CACHE_DIR, 'browser');
|
|
@@ -309,6 +314,8 @@ export function getCacheDir() { return CACHE_DIR; }
|
|
|
309
314
|
export function getPackagesDir() { return PACKAGES_DIR; }
|
|
310
315
|
/** Path to routine YAML definitions (~/.agents/routines/). */
|
|
311
316
|
export function getRoutinesDir() { return process.env.AGENTS_ROUTINES_DIR ?? ROUTINES_DIR; }
|
|
317
|
+
/** Path to named project definitions (~/.agents/projects/). */
|
|
318
|
+
export function getProjectsDir() { return process.env.AGENTS_PROJECTS_DIR ?? PROJECTS_DIR; }
|
|
312
319
|
/**
|
|
313
320
|
* Path to webhook handler YAML definitions (~/.agents/webhooks/). Handlers are
|
|
314
321
|
* one-off triggers for agents/workflows/commands/routines, layered the same way
|
|
@@ -436,6 +443,15 @@ export function getCloudDir() { return CLOUD_DIR; }
|
|
|
436
443
|
export function getTerminalsDir() { return TERMINALS_DIR; }
|
|
437
444
|
/** Path to runtime logs (~/.agents/.cache/logs/). */
|
|
438
445
|
export function getLogsDir() { return LOGS_DIR; }
|
|
446
|
+
/**
|
|
447
|
+
* Path to disposable performance samples (~/.agents/.cache/perf/).
|
|
448
|
+
* Holds `perf.db` + a hook-shim spool. Loss is acceptable — wipe freely.
|
|
449
|
+
*/
|
|
450
|
+
export function getPerfDir() { return PERF_DIR; }
|
|
451
|
+
/** Path to the perf SQLite warehouse (~/.agents/.cache/perf/perf.db). */
|
|
452
|
+
export function getPerfDbPath() { return path.join(PERF_DIR, 'perf.db'); }
|
|
453
|
+
/** Path to the hook-shim NDJSON spool drained into perf.db on open. */
|
|
454
|
+
export function getPerfSpoolPath() { return path.join(PERF_DIR, 'spool.jsonl'); }
|
|
439
455
|
/** Path to per-process runtime state (~/.agents/.cache/state/). */
|
|
440
456
|
export function getRuntimeStateDir() { return RUNTIME_STATE_DIR; }
|
|
441
457
|
/** Path to companion-extension scratch (~/.agents/.cache/companion/). */
|
|
@@ -6,7 +6,15 @@ import { itermBackend } from './iterm.js';
|
|
|
6
6
|
import { ghosttyBackend } from './ghostty.js';
|
|
7
7
|
import { tmuxBackend } from './tmux.js';
|
|
8
8
|
import { vscodiumAgentBackend } from './vscodium-agent.js';
|
|
9
|
-
|
|
9
|
+
import { terminalAppBackend } from './terminal-app.js';
|
|
10
|
+
/**
|
|
11
|
+
* All known interactive backends, keyed by id.
|
|
12
|
+
*
|
|
13
|
+
* Insertion order is the preference order `availableBackends` returns, and
|
|
14
|
+
* `terminal` (Terminal.app) sits LAST deliberately: it is the every-Mac floor, so
|
|
15
|
+
* a caller that falls back to the first available backend still prefers a real
|
|
16
|
+
* terminal the user chose to install.
|
|
17
|
+
*/
|
|
10
18
|
export declare const BACKENDS: Record<Backend, TerminalBackend>;
|
|
11
19
|
/**
|
|
12
20
|
* The backend for the terminal the CLI is currently running in, or null if we
|
|
@@ -16,4 +24,4 @@ export declare const BACKENDS: Record<Backend, TerminalBackend>;
|
|
|
16
24
|
export declare function detectCurrentBackend(ctx: EngineContext): Backend | null;
|
|
17
25
|
/** Backends that can actually be driven in this context. */
|
|
18
26
|
export declare function availableBackends(ctx: EngineContext): TerminalBackend[];
|
|
19
|
-
export { itermBackend, ghosttyBackend, tmuxBackend, vscodiumAgentBackend };
|
|
27
|
+
export { itermBackend, ghosttyBackend, tmuxBackend, vscodiumAgentBackend, terminalAppBackend };
|
|
@@ -2,12 +2,21 @@ import { itermBackend } from './iterm.js';
|
|
|
2
2
|
import { ghosttyBackend } from './ghostty.js';
|
|
3
3
|
import { tmuxBackend } from './tmux.js';
|
|
4
4
|
import { vscodiumAgentBackend } from './vscodium-agent.js';
|
|
5
|
-
|
|
5
|
+
import { terminalAppBackend } from './terminal-app.js';
|
|
6
|
+
/**
|
|
7
|
+
* All known interactive backends, keyed by id.
|
|
8
|
+
*
|
|
9
|
+
* Insertion order is the preference order `availableBackends` returns, and
|
|
10
|
+
* `terminal` (Terminal.app) sits LAST deliberately: it is the every-Mac floor, so
|
|
11
|
+
* a caller that falls back to the first available backend still prefers a real
|
|
12
|
+
* terminal the user chose to install.
|
|
13
|
+
*/
|
|
6
14
|
export const BACKENDS = {
|
|
7
15
|
iterm: itermBackend,
|
|
8
16
|
ghostty: ghosttyBackend,
|
|
9
17
|
tmux: tmuxBackend,
|
|
10
18
|
'vscodium-agent': vscodiumAgentBackend,
|
|
19
|
+
terminal: terminalAppBackend,
|
|
11
20
|
};
|
|
12
21
|
/**
|
|
13
22
|
* The backend for the terminal the CLI is currently running in, or null if we
|
|
@@ -22,10 +31,13 @@ export function detectCurrentBackend(ctx) {
|
|
|
22
31
|
return 'iterm';
|
|
23
32
|
if (term.includes('ghostty'))
|
|
24
33
|
return 'ghostty';
|
|
34
|
+
// Terminal.app sets TERM_PROGRAM=Apple_Terminal.
|
|
35
|
+
if (term.includes('apple_terminal'))
|
|
36
|
+
return 'terminal';
|
|
25
37
|
return null;
|
|
26
38
|
}
|
|
27
39
|
/** Backends that can actually be driven in this context. */
|
|
28
40
|
export function availableBackends(ctx) {
|
|
29
41
|
return Object.values(BACKENDS).filter((b) => b.isAvailable(ctx));
|
|
30
42
|
}
|
|
31
|
-
export { itermBackend, ghosttyBackend, tmuxBackend, vscodiumAgentBackend };
|
|
43
|
+
export { itermBackend, ghosttyBackend, tmuxBackend, vscodiumAgentBackend, terminalAppBackend };
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { TerminalBackend } from '../types.js';
|
|
2
|
+
/**
|
|
3
|
+
* AppleScript that opens a Terminal.app tab running the command.
|
|
4
|
+
*
|
|
5
|
+
* `do script` with no `in` target opens a NEW window; with a front window
|
|
6
|
+
* present, `tell application "System Events" to keystroke "t"` would be the only
|
|
7
|
+
* way to force a tab, and that needs an Accessibility grant. So: a window when
|
|
8
|
+
* none exists, otherwise `do script … in front window`, which Terminal renders
|
|
9
|
+
* as a new tab of that window when its "New tabs" setting is on and a new
|
|
10
|
+
* window otherwise. Either way the command runs in a fresh, visible surface.
|
|
11
|
+
*/
|
|
12
|
+
export declare function terminalAppTabScript(cwd: string, command: string[]): string;
|
|
13
|
+
export declare const terminalAppBackend: TerminalBackend;
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Terminal.app backend — drives macOS Terminal via AppleScript (`osascript`).
|
|
3
|
+
*
|
|
4
|
+
* This is the floor of the backend list: every Mac has Terminal.app, so a GUI
|
|
5
|
+
* caller that can name no better terminal still gets a surface. It is also what
|
|
6
|
+
* the menu bar's "New Session" always used before the engine took that path
|
|
7
|
+
* over, so registering it keeps a Terminal-only Mac working exactly as it did.
|
|
8
|
+
*
|
|
9
|
+
* Terminal.app has no scriptable split — `do script` only ever makes a tab (or a
|
|
10
|
+
* window). A split request therefore opens a TAB, which is stated here and in
|
|
11
|
+
* `buildSplit` rather than silently pretending the pane happened.
|
|
12
|
+
*/
|
|
13
|
+
import * as fs from 'fs';
|
|
14
|
+
import { appleScriptStr } from '../quote.js';
|
|
15
|
+
import { loginExec, iLoginShell } from '../shell.js';
|
|
16
|
+
const TERMINAL_APP = '/System/Applications/Utilities/Terminal.app';
|
|
17
|
+
const TERMINAL_APP_LEGACY = '/Applications/Utilities/Terminal.app';
|
|
18
|
+
function appExists(p) {
|
|
19
|
+
try {
|
|
20
|
+
return fs.existsSync(p);
|
|
21
|
+
}
|
|
22
|
+
catch {
|
|
23
|
+
return false;
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* AppleScript that opens a Terminal.app tab running the command.
|
|
28
|
+
*
|
|
29
|
+
* `do script` with no `in` target opens a NEW window; with a front window
|
|
30
|
+
* present, `tell application "System Events" to keystroke "t"` would be the only
|
|
31
|
+
* way to force a tab, and that needs an Accessibility grant. So: a window when
|
|
32
|
+
* none exists, otherwise `do script … in front window`, which Terminal renders
|
|
33
|
+
* as a new tab of that window when its "New tabs" setting is on and a new
|
|
34
|
+
* window otherwise. Either way the command runs in a fresh, visible surface.
|
|
35
|
+
*/
|
|
36
|
+
export function terminalAppTabScript(cwd, command) {
|
|
37
|
+
const cmd = appleScriptStr(iLoginShell(loginExec(cwd, command)));
|
|
38
|
+
return [
|
|
39
|
+
'tell application "Terminal"',
|
|
40
|
+
' activate',
|
|
41
|
+
' if (count of windows) is 0 then',
|
|
42
|
+
` do script ${cmd}`,
|
|
43
|
+
' else',
|
|
44
|
+
` do script ${cmd} in front window`,
|
|
45
|
+
' end if',
|
|
46
|
+
'end tell',
|
|
47
|
+
].join('\n');
|
|
48
|
+
}
|
|
49
|
+
export const terminalAppBackend = {
|
|
50
|
+
id: 'terminal',
|
|
51
|
+
label: 'Terminal',
|
|
52
|
+
/**
|
|
53
|
+
* Terminal.app is scriptable only from a session that can reach the GUI login
|
|
54
|
+
* — an `osascript` fired over plain SSH gets "Application isn't running" — so
|
|
55
|
+
* an SSH-hosted context reports it unavailable rather than launching into a
|
|
56
|
+
* window nobody can see. (A launchd GUI agent like the menu bar helper has no
|
|
57
|
+
* SSH_* vars and is available.)
|
|
58
|
+
*/
|
|
59
|
+
isAvailable(ctx) {
|
|
60
|
+
if (ctx.platform !== 'darwin')
|
|
61
|
+
return false;
|
|
62
|
+
if (ctx.env.SSH_CONNECTION || ctx.env.SSH_TTY)
|
|
63
|
+
return false;
|
|
64
|
+
return appExists(TERMINAL_APP) || appExists(TERMINAL_APP_LEGACY);
|
|
65
|
+
},
|
|
66
|
+
buildTab(cwd, command) {
|
|
67
|
+
return { argv: ['osascript', '-e', terminalAppTabScript(cwd, command)] };
|
|
68
|
+
},
|
|
69
|
+
/** Terminal.app cannot split by script — a split request opens a tab instead. */
|
|
70
|
+
buildSplit(cwd, command, _direction) {
|
|
71
|
+
return { argv: ['osascript', '-e', terminalAppTabScript(cwd, command)] };
|
|
72
|
+
},
|
|
73
|
+
};
|
|
@@ -8,7 +8,8 @@
|
|
|
8
8
|
*/
|
|
9
9
|
export type { Backend, SplitDirection, Layout, EngineContext, LaunchSpec, LaunchRequest, LaunchResult, TerminalBackend, } from './types.js';
|
|
10
10
|
export { currentContext } from './types.js';
|
|
11
|
-
export { BACKENDS, detectCurrentBackend, availableBackends, itermBackend, ghosttyBackend, tmuxBackend, vscodiumAgentBackend } from './backends/index.js';
|
|
11
|
+
export { BACKENDS, detectCurrentBackend, availableBackends, itermBackend, ghosttyBackend, tmuxBackend, vscodiumAgentBackend, terminalAppBackend } from './backends/index.js';
|
|
12
|
+
export { SESSION_HOST_BACKENDS, backendFromSessions, resolveLaunchBackend, describeBackendChoice, type SessionHostSample, type BackendSource, type LaunchBackendChoice, } from './preferred.js';
|
|
12
13
|
export { makeVscodiumAgentBackend, spawnUri, EDITOR_VARIANTS, type EditorVariant } from './backends/vscodium-agent.js';
|
|
13
14
|
export { planLayouts, type Packing } from './policy.js';
|
|
14
15
|
export { specForRequest, buildRequests, openSurface, openSurfaces, type OpenOptions, type OpenManyOptions, type BuildRequestsOptions, type SurfaceItem, } from './engine.js';
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
export { currentContext } from './types.js';
|
|
2
|
-
export { BACKENDS, detectCurrentBackend, availableBackends, itermBackend, ghosttyBackend, tmuxBackend, vscodiumAgentBackend } from './backends/index.js';
|
|
2
|
+
export { BACKENDS, detectCurrentBackend, availableBackends, itermBackend, ghosttyBackend, tmuxBackend, vscodiumAgentBackend, terminalAppBackend } from './backends/index.js';
|
|
3
|
+
export { SESSION_HOST_BACKENDS, backendFromSessions, resolveLaunchBackend, describeBackendChoice, } from './preferred.js';
|
|
3
4
|
export { makeVscodiumAgentBackend, spawnUri, EDITOR_VARIANTS } from './backends/vscodium-agent.js';
|
|
4
5
|
export { planLayouts } from './policy.js';
|
|
5
6
|
export { specForRequest, buildRequests, openSurface, openSurfaces, } from './engine.js';
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Which terminal to launch a NEW surface into, for callers that aren't in one.
|
|
3
|
+
*
|
|
4
|
+
* `detectCurrentBackend` answers this from `$TMUX` / `$TERM_PROGRAM`, which is
|
|
5
|
+
* correct for a command the user typed in a terminal — and useless to a GUI
|
|
6
|
+
* caller. The menu-bar helper is launched by launchd with no terminal in its
|
|
7
|
+
* ancestry, so it used to hardcode AppleScript against Terminal.app and opened
|
|
8
|
+
* every "New Session" there no matter what the user actually works in.
|
|
9
|
+
*
|
|
10
|
+
* The signal it was missing is already on disk: `agents sessions --active`
|
|
11
|
+
* attributes every live session to its host app (`ActiveSession.host`, resolved
|
|
12
|
+
* by walking the process table in session/active.ts). So the terminal the user
|
|
13
|
+
* demonstrably runs agents in is the terminal a new session should open in —
|
|
14
|
+
* that's what this module resolves, and it is the reason the menu bar now opens
|
|
15
|
+
* Ghostty for a Ghostty user and iTerm for an iTerm user.
|
|
16
|
+
*
|
|
17
|
+
* Resolution order (each step is skipped when it names nothing DRIVABLE here):
|
|
18
|
+
* 1. the terminal this process is running in -> 'current-terminal'
|
|
19
|
+
* 2. the host app of the most recent live session -> 'active-session'
|
|
20
|
+
* 3. the first available backend (Terminal.app is the every-Mac floor) -> 'available'
|
|
21
|
+
*/
|
|
22
|
+
import type { Backend, EngineContext } from './types.js';
|
|
23
|
+
/**
|
|
24
|
+
* `ActiveSession.host` -> the backend that can open a new surface in it.
|
|
25
|
+
*
|
|
26
|
+
* Deliberately partial. A host is listed ONLY when the engine can really drive
|
|
27
|
+
* it, because a wrong mapping opens the wrong app and looks like success:
|
|
28
|
+
* - `code` / `cursor` / `windsurf` are absent even though the engine has an
|
|
29
|
+
* editor backend — the registered `vscodium-agent` is bound to the VSCodium
|
|
30
|
+
* variant (backends/vscodium-agent.ts: `EDITOR_VARIANTS[0]`), so mapping
|
|
31
|
+
* Cursor to it would open VSCodium instead of Cursor.
|
|
32
|
+
* - `warp` / `kitty` / `wezterm` / `alacritty` / `hyper` / `screen` are absent
|
|
33
|
+
* because no backend drives them yet.
|
|
34
|
+
* An unmapped host isn't an error — resolution simply moves to the next session,
|
|
35
|
+
* and ultimately to the available-backend floor.
|
|
36
|
+
*/
|
|
37
|
+
export declare const SESSION_HOST_BACKENDS: Readonly<Record<string, Backend>>;
|
|
38
|
+
/** The subset of an ActiveSession this resolver reads. */
|
|
39
|
+
export interface SessionHostSample {
|
|
40
|
+
host?: string;
|
|
41
|
+
/**
|
|
42
|
+
* For a tmux-hosted session: the app its attached tmux client is in
|
|
43
|
+
* (`ActiveSession.viewingIn.app`). This is load-bearing, not a nicety —
|
|
44
|
+
* `agents run` wraps interactive runs in tmux, so a session the user started
|
|
45
|
+
* in Ghostty is attributed `host: 'tmux'` and the terminal it is actually
|
|
46
|
+
* displayed in is only knowable through the attached client. Takes precedence
|
|
47
|
+
* over `host`, which for these sessions names the multiplexer, not a terminal.
|
|
48
|
+
*/
|
|
49
|
+
viewingApp?: string;
|
|
50
|
+
lastActivityMs?: number;
|
|
51
|
+
startedAtMs?: number;
|
|
52
|
+
}
|
|
53
|
+
/** Where the chosen backend came from — reported so a caller can say why. */
|
|
54
|
+
export type BackendSource = 'forced' | 'current-terminal' | 'active-session' | 'available';
|
|
55
|
+
export interface LaunchBackendChoice {
|
|
56
|
+
backend: Backend;
|
|
57
|
+
source: BackendSource;
|
|
58
|
+
/** The `ActiveSession.host` that selected it, when source is 'active-session'. */
|
|
59
|
+
host?: string;
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* Injection seam so the PRECEDENCE is testable without the machine's installed
|
|
63
|
+
* apps. `isAvailable` for iterm/ghostty/terminal probes `/Applications`, so a
|
|
64
|
+
* test that just passes `platform: 'darwin'` silently depends on the host having
|
|
65
|
+
* those apps — it passes on a dev Mac and fails on a Linux CI runner. Same shape
|
|
66
|
+
* as `ViewingInDeps` in session/viewing-in.ts.
|
|
67
|
+
*/
|
|
68
|
+
export interface BackendResolveDeps {
|
|
69
|
+
/** Defaults to the real backend availability probe. */
|
|
70
|
+
isAvailable?: (backend: Backend, ctx: EngineContext) => boolean;
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* The backend for the terminal the user's most recent live session runs in, or
|
|
74
|
+
* null when no live session names a host this engine can drive. Pure — the
|
|
75
|
+
* caller supplies the sessions, so the precedence is unit-testable without a
|
|
76
|
+
* process table.
|
|
77
|
+
*/
|
|
78
|
+
export declare function backendFromSessions(sessions: SessionHostSample[], ctx: EngineContext, deps?: BackendResolveDeps): {
|
|
79
|
+
backend: Backend;
|
|
80
|
+
host: string;
|
|
81
|
+
} | null;
|
|
82
|
+
/**
|
|
83
|
+
* Resolve the terminal to open a new surface in. Pure. Returns null when this
|
|
84
|
+
* machine has no drivable terminal at all (Linux with no tmux, an SSH session),
|
|
85
|
+
* which callers must handle — never a guess that silently goes nowhere.
|
|
86
|
+
*/
|
|
87
|
+
export declare function resolveLaunchBackend(ctx: EngineContext, sessions?: SessionHostSample[], deps?: BackendResolveDeps): LaunchBackendChoice | null;
|
|
88
|
+
/** One line explaining a choice, for the `agents run --terminal` preamble. */
|
|
89
|
+
export declare function describeBackendChoice(choice: LaunchBackendChoice): string;
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import { BACKENDS, detectCurrentBackend } from './backends/index.js';
|
|
2
|
+
/**
|
|
3
|
+
* `ActiveSession.host` -> the backend that can open a new surface in it.
|
|
4
|
+
*
|
|
5
|
+
* Deliberately partial. A host is listed ONLY when the engine can really drive
|
|
6
|
+
* it, because a wrong mapping opens the wrong app and looks like success:
|
|
7
|
+
* - `code` / `cursor` / `windsurf` are absent even though the engine has an
|
|
8
|
+
* editor backend — the registered `vscodium-agent` is bound to the VSCodium
|
|
9
|
+
* variant (backends/vscodium-agent.ts: `EDITOR_VARIANTS[0]`), so mapping
|
|
10
|
+
* Cursor to it would open VSCodium instead of Cursor.
|
|
11
|
+
* - `warp` / `kitty` / `wezterm` / `alacritty` / `hyper` / `screen` are absent
|
|
12
|
+
* because no backend drives them yet.
|
|
13
|
+
* An unmapped host isn't an error — resolution simply moves to the next session,
|
|
14
|
+
* and ultimately to the available-backend floor.
|
|
15
|
+
*/
|
|
16
|
+
export const SESSION_HOST_BACKENDS = Object.freeze({
|
|
17
|
+
iterm: 'iterm',
|
|
18
|
+
ghostty: 'ghostty',
|
|
19
|
+
terminal: 'terminal',
|
|
20
|
+
tmux: 'tmux',
|
|
21
|
+
codium: 'vscodium-agent',
|
|
22
|
+
});
|
|
23
|
+
/** Most recent first. Falls back to start time, then to the given order. */
|
|
24
|
+
function byRecency(a, b) {
|
|
25
|
+
const at = a.lastActivityMs ?? a.startedAtMs ?? 0;
|
|
26
|
+
const bt = b.lastActivityMs ?? b.startedAtMs ?? 0;
|
|
27
|
+
return bt - at;
|
|
28
|
+
}
|
|
29
|
+
const realIsAvailable = (backend, ctx) => BACKENDS[backend].isAvailable(ctx);
|
|
30
|
+
/**
|
|
31
|
+
* The backend for the terminal the user's most recent live session runs in, or
|
|
32
|
+
* null when no live session names a host this engine can drive. Pure — the
|
|
33
|
+
* caller supplies the sessions, so the precedence is unit-testable without a
|
|
34
|
+
* process table.
|
|
35
|
+
*/
|
|
36
|
+
export function backendFromSessions(sessions, ctx, deps = {}) {
|
|
37
|
+
const isAvailable = deps.isAvailable ?? realIsAvailable;
|
|
38
|
+
for (const s of [...sessions].sort(byRecency)) {
|
|
39
|
+
// The app an attached tmux client is in beats the multiplexer name.
|
|
40
|
+
const host = s.viewingApp ?? s.host;
|
|
41
|
+
if (!host)
|
|
42
|
+
continue;
|
|
43
|
+
// hasOwn, not a bare index: the host is data, and `SESSION_HOST_BACKENDS['constructor']`
|
|
44
|
+
// would otherwise hand back a prototype member that BACKENDS cannot key on.
|
|
45
|
+
if (!Object.hasOwn(SESSION_HOST_BACKENDS, host))
|
|
46
|
+
continue;
|
|
47
|
+
const backend = SESSION_HOST_BACKENDS[host];
|
|
48
|
+
if (!backend)
|
|
49
|
+
continue;
|
|
50
|
+
if (!isAvailable(backend, ctx))
|
|
51
|
+
continue;
|
|
52
|
+
return { backend, host };
|
|
53
|
+
}
|
|
54
|
+
return null;
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* Resolve the terminal to open a new surface in. Pure. Returns null when this
|
|
58
|
+
* machine has no drivable terminal at all (Linux with no tmux, an SSH session),
|
|
59
|
+
* which callers must handle — never a guess that silently goes nowhere.
|
|
60
|
+
*/
|
|
61
|
+
export function resolveLaunchBackend(ctx, sessions = [], deps = {}) {
|
|
62
|
+
const isAvailable = deps.isAvailable ?? realIsAvailable;
|
|
63
|
+
const current = detectCurrentBackend(ctx);
|
|
64
|
+
if (current && isAvailable(current, ctx)) {
|
|
65
|
+
return { backend: current, source: 'current-terminal' };
|
|
66
|
+
}
|
|
67
|
+
const fromSession = backendFromSessions(sessions, ctx, deps);
|
|
68
|
+
if (fromSession) {
|
|
69
|
+
return { backend: fromSession.backend, source: 'active-session', host: fromSession.host };
|
|
70
|
+
}
|
|
71
|
+
const first = Object.keys(BACKENDS).find((b) => isAvailable(b, ctx));
|
|
72
|
+
return first ? { backend: first, source: 'available' } : null;
|
|
73
|
+
}
|
|
74
|
+
/** One line explaining a choice, for the `agents run --terminal` preamble. */
|
|
75
|
+
export function describeBackendChoice(choice) {
|
|
76
|
+
const label = BACKENDS[choice.backend].label;
|
|
77
|
+
switch (choice.source) {
|
|
78
|
+
case 'forced':
|
|
79
|
+
return `${label} (you asked for it)`;
|
|
80
|
+
case 'current-terminal':
|
|
81
|
+
return `${label} (the terminal you're in)`;
|
|
82
|
+
case 'active-session':
|
|
83
|
+
return `${label} (where your ${choice.host} sessions run)`;
|
|
84
|
+
case 'available':
|
|
85
|
+
return `${label} (no running session named a terminal)`;
|
|
86
|
+
}
|
|
87
|
+
}
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `agents run … --terminal` — re-open this exact run as a tab in a real terminal.
|
|
3
|
+
*
|
|
4
|
+
* A GUI caller (the menu bar's "New Session") cannot host a TUI, so it has to
|
|
5
|
+
* hand the run to a terminal. It used to do that by hardcoding AppleScript at
|
|
6
|
+
* Terminal.app; now it appends `--terminal` and the CLI decides WHICH terminal
|
|
7
|
+
* from the user's own live sessions (preferred.ts) and opens the tab through the
|
|
8
|
+
* launch engine — the same engine `sessions resume` and `sessions focus` use.
|
|
9
|
+
*
|
|
10
|
+
* The re-invocation is the caller's own argv with the `--terminal` flag removed,
|
|
11
|
+
* so every other flag (`--mode`, `--cwd`, a `--` passthrough) rides along
|
|
12
|
+
* untouched and there is no second place that knows how to spell a run.
|
|
13
|
+
*/
|
|
14
|
+
import type { Backend, EngineContext } from './types.js';
|
|
15
|
+
import type { ActiveSession } from '../session/active.js';
|
|
16
|
+
import { type LaunchBackendChoice, type SessionHostSample } from './preferred.js';
|
|
17
|
+
/** Backends a user may name in `--terminal <backend>`. */
|
|
18
|
+
export declare const TERMINAL_FLAG_BACKENDS: Backend[];
|
|
19
|
+
/**
|
|
20
|
+
* Validate a `--terminal <value>`. Returns the backend, or an error message
|
|
21
|
+
* naming the valid ids — never a silent fallback to auto-detection, which would
|
|
22
|
+
* open a terminal the user did not ask for.
|
|
23
|
+
*/
|
|
24
|
+
export declare function parseTerminalFlag(value: unknown): {
|
|
25
|
+
backend?: Backend;
|
|
26
|
+
error?: string;
|
|
27
|
+
};
|
|
28
|
+
/**
|
|
29
|
+
* The argv to re-invoke, with `--terminal` (and the value commander consumed for
|
|
30
|
+
* it) removed. `consumedValue` is the parsed option value when it is a string —
|
|
31
|
+
* that is the only token after the flag that belongs to it, so a prompt or a
|
|
32
|
+
* following flag is never eaten.
|
|
33
|
+
*/
|
|
34
|
+
export declare function stripTerminalFlag(argv: string[], consumedValue?: string): string[];
|
|
35
|
+
/** `agents run …` as a shell-safe command line for the surface to exec. */
|
|
36
|
+
export declare function buildRunCommand(argv: string[]): string[];
|
|
37
|
+
/**
|
|
38
|
+
* Turn live sessions into the samples the resolver reads, filling in the app
|
|
39
|
+
* each tmux-hosted session is currently VIEWED in.
|
|
40
|
+
*
|
|
41
|
+
* This step is what makes detection work for the common case: `agents run`
|
|
42
|
+
* wraps interactive runs in tmux, so a session the user started in Ghostty is
|
|
43
|
+
* attributed `host: 'tmux'` on the discovery path and would otherwise name no
|
|
44
|
+
* terminal at all. `resolveViewingIn` walks the attached tmux client's pid up to
|
|
45
|
+
* its host app — the same resolver `agents sessions` uses to print
|
|
46
|
+
* "viewing in Ghostty tab 2". Sessions that are detached (no client attached)
|
|
47
|
+
* legitimately have no viewer and keep their `tmux` host.
|
|
48
|
+
*
|
|
49
|
+
* One inherited nuance: `resolveViewingIn` labels a client whose app it cannot
|
|
50
|
+
* identify `'terminal'` (viewing-in.ts:87), so a tmux session viewed from an
|
|
51
|
+
* unrecognized emulator resolves to Terminal.app rather than falling through.
|
|
52
|
+
* That lands on the same every-Mac floor the fallback chain ends at anyway, so
|
|
53
|
+
* it costs nothing here — but it is a default, not a detection.
|
|
54
|
+
*
|
|
55
|
+
* Best-effort: any probe failure degrades to the plain host, never throws.
|
|
56
|
+
*/
|
|
57
|
+
export declare function toHostSamples(sessions: ActiveSession[]): Promise<SessionHostSample[]>;
|
|
58
|
+
export interface OpenRunSurfaceParams {
|
|
59
|
+
/** This process's argv after the program name (i.e. `['run','claude',…]`). */
|
|
60
|
+
argv: string[];
|
|
61
|
+
/** The parsed `--terminal` value, when the user named a backend. */
|
|
62
|
+
forced?: Backend;
|
|
63
|
+
/** The value commander consumed for `--terminal`, so it can be stripped. */
|
|
64
|
+
consumedValue?: string;
|
|
65
|
+
cwd: string;
|
|
66
|
+
/** Live sessions, used to detect the terminal the user actually works in. */
|
|
67
|
+
sessions: SessionHostSample[];
|
|
68
|
+
ctx: EngineContext;
|
|
69
|
+
}
|
|
70
|
+
export interface OpenRunSurfaceResult {
|
|
71
|
+
ok: boolean;
|
|
72
|
+
choice?: LaunchBackendChoice;
|
|
73
|
+
/** Human line describing the terminal that was chosen. */
|
|
74
|
+
description?: string;
|
|
75
|
+
error?: string;
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* Open the run as a tab in the resolved terminal. Never throws — a failure comes
|
|
79
|
+
* back as `ok: false` with the reason, so the caller can tell the user rather
|
|
80
|
+
* than exiting silently.
|
|
81
|
+
*/
|
|
82
|
+
export declare function openRunInTerminal(params: OpenRunSurfaceParams): Promise<OpenRunSurfaceResult>;
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
import { BACKENDS } from './backends/index.js';
|
|
2
|
+
import { openSurface } from './engine.js';
|
|
3
|
+
import { getCliLaunch } from '../cli-entry.js';
|
|
4
|
+
import { shellQuote } from './quote.js';
|
|
5
|
+
import { resolveLaunchBackend, describeBackendChoice, } from './preferred.js';
|
|
6
|
+
/** Backends a user may name in `--terminal <backend>`. */
|
|
7
|
+
export const TERMINAL_FLAG_BACKENDS = Object.keys(BACKENDS);
|
|
8
|
+
/**
|
|
9
|
+
* Validate a `--terminal <value>`. Returns the backend, or an error message
|
|
10
|
+
* naming the valid ids — never a silent fallback to auto-detection, which would
|
|
11
|
+
* open a terminal the user did not ask for.
|
|
12
|
+
*/
|
|
13
|
+
export function parseTerminalFlag(value) {
|
|
14
|
+
if (value === undefined || value === true || value === '')
|
|
15
|
+
return {};
|
|
16
|
+
const raw = String(value);
|
|
17
|
+
if (TERMINAL_FLAG_BACKENDS.includes(raw))
|
|
18
|
+
return { backend: raw };
|
|
19
|
+
// `--terminal [backend]` takes an OPTIONAL value, and commander assigns the
|
|
20
|
+
// next non-option token to it — so `agents run claude --terminal "fix the bug"`
|
|
21
|
+
// lands the prompt here. Say that, or the user just sees their prompt called a
|
|
22
|
+
// bad backend name and has no idea why.
|
|
23
|
+
const looksLikeAPrompt = /\s/.test(raw) || raw.length > 24;
|
|
24
|
+
const hint = looksLikeAPrompt
|
|
25
|
+
? ` That looks like a prompt: put it BEFORE the flag — agents run <agent> "${raw.length > 40 ? `${raw.slice(0, 40)}…` : raw}" --terminal.`
|
|
26
|
+
: '';
|
|
27
|
+
return {
|
|
28
|
+
error: `Unknown --terminal backend '${raw}'. Use one of: ${TERMINAL_FLAG_BACKENDS.join(', ')} (or pass --terminal alone to auto-detect).${hint}`,
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* The argv to re-invoke, with `--terminal` (and the value commander consumed for
|
|
33
|
+
* it) removed. `consumedValue` is the parsed option value when it is a string —
|
|
34
|
+
* that is the only token after the flag that belongs to it, so a prompt or a
|
|
35
|
+
* following flag is never eaten.
|
|
36
|
+
*/
|
|
37
|
+
export function stripTerminalFlag(argv, consumedValue) {
|
|
38
|
+
const out = [];
|
|
39
|
+
for (let i = 0; i < argv.length; i++) {
|
|
40
|
+
const tok = argv[i];
|
|
41
|
+
// Everything past a bare `--` is forwarded verbatim to the agent's own CLI
|
|
42
|
+
// (`agents run kimi -- --terminal`), so it is not ours to rewrite.
|
|
43
|
+
if (tok === '--') {
|
|
44
|
+
out.push(...argv.slice(i));
|
|
45
|
+
break;
|
|
46
|
+
}
|
|
47
|
+
if (tok === '--terminal') {
|
|
48
|
+
if (consumedValue !== undefined && argv[i + 1] === consumedValue)
|
|
49
|
+
i++;
|
|
50
|
+
continue;
|
|
51
|
+
}
|
|
52
|
+
if (tok.startsWith('--terminal='))
|
|
53
|
+
continue;
|
|
54
|
+
out.push(tok);
|
|
55
|
+
}
|
|
56
|
+
return out;
|
|
57
|
+
}
|
|
58
|
+
/** `agents run …` as a shell-safe command line for the surface to exec. */
|
|
59
|
+
export function buildRunCommand(argv) {
|
|
60
|
+
const { command, args } = getCliLaunch(argv);
|
|
61
|
+
return [command, ...args].map(shellQuote);
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* Turn live sessions into the samples the resolver reads, filling in the app
|
|
65
|
+
* each tmux-hosted session is currently VIEWED in.
|
|
66
|
+
*
|
|
67
|
+
* This step is what makes detection work for the common case: `agents run`
|
|
68
|
+
* wraps interactive runs in tmux, so a session the user started in Ghostty is
|
|
69
|
+
* attributed `host: 'tmux'` on the discovery path and would otherwise name no
|
|
70
|
+
* terminal at all. `resolveViewingIn` walks the attached tmux client's pid up to
|
|
71
|
+
* its host app — the same resolver `agents sessions` uses to print
|
|
72
|
+
* "viewing in Ghostty tab 2". Sessions that are detached (no client attached)
|
|
73
|
+
* legitimately have no viewer and keep their `tmux` host.
|
|
74
|
+
*
|
|
75
|
+
* One inherited nuance: `resolveViewingIn` labels a client whose app it cannot
|
|
76
|
+
* identify `'terminal'` (viewing-in.ts:87), so a tmux session viewed from an
|
|
77
|
+
* unrecognized emulator resolves to Terminal.app rather than falling through.
|
|
78
|
+
* That lands on the same every-Mac floor the fallback chain ends at anyway, so
|
|
79
|
+
* it costs nothing here — but it is a default, not a detection.
|
|
80
|
+
*
|
|
81
|
+
* Best-effort: any probe failure degrades to the plain host, never throws.
|
|
82
|
+
*/
|
|
83
|
+
export async function toHostSamples(sessions) {
|
|
84
|
+
const samples = sessions.map((s) => ({
|
|
85
|
+
host: s.host,
|
|
86
|
+
lastActivityMs: s.lastActivityMs,
|
|
87
|
+
startedAtMs: s.startedAtMs,
|
|
88
|
+
}));
|
|
89
|
+
const tmuxIdx = sessions
|
|
90
|
+
.map((s, i) => ({ s, i }))
|
|
91
|
+
.filter(({ s }) => s.provenance?.mux?.kind === 'tmux' && s.provenance.mux.pane);
|
|
92
|
+
if (tmuxIdx.length === 0)
|
|
93
|
+
return samples;
|
|
94
|
+
try {
|
|
95
|
+
const { enumerateGhosttyTabs } = await import('../session/ghostty-tabs.js');
|
|
96
|
+
const { mapPanesToTargets, listClients } = await import('../tmux/session.js');
|
|
97
|
+
const { resolveViewingIn } = await import('../session/viewing-in.js');
|
|
98
|
+
// One Ghostty enumeration shared across sockets, as the sessions renderer does.
|
|
99
|
+
const ghosttySurfaces = await enumerateGhosttyTabs();
|
|
100
|
+
const sockets = new Set(tmuxIdx.map(({ s }) => s.provenance.mux.socket));
|
|
101
|
+
for (const socket of sockets) {
|
|
102
|
+
const paneToTarget = await mapPanesToTargets(socket);
|
|
103
|
+
if (paneToTarget.size === 0)
|
|
104
|
+
continue;
|
|
105
|
+
const clients = await listClients(socket);
|
|
106
|
+
for (const { s, i } of tmuxIdx) {
|
|
107
|
+
if (s.provenance.mux.socket !== socket)
|
|
108
|
+
continue;
|
|
109
|
+
const viewing = await resolveViewingIn(s, clients, { paneToTarget, ghosttySurfaces });
|
|
110
|
+
if (viewing)
|
|
111
|
+
samples[i].viewingApp = viewing.app;
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
catch {
|
|
116
|
+
// tmux/Ghostty probes are best-effort; fall back to the plain host values.
|
|
117
|
+
}
|
|
118
|
+
return samples;
|
|
119
|
+
}
|
|
120
|
+
/**
|
|
121
|
+
* Open the run as a tab in the resolved terminal. Never throws — a failure comes
|
|
122
|
+
* back as `ok: false` with the reason, so the caller can tell the user rather
|
|
123
|
+
* than exiting silently.
|
|
124
|
+
*/
|
|
125
|
+
export async function openRunInTerminal(params) {
|
|
126
|
+
const choice = params.forced
|
|
127
|
+
? { backend: params.forced, source: 'forced' }
|
|
128
|
+
: resolveLaunchBackend(params.ctx, params.sessions);
|
|
129
|
+
if (!choice) {
|
|
130
|
+
return {
|
|
131
|
+
ok: false,
|
|
132
|
+
error: 'No terminal this machine can drive (need iTerm, Ghostty, Terminal.app, VSCodium, or a tmux session). Run without --terminal.',
|
|
133
|
+
};
|
|
134
|
+
}
|
|
135
|
+
if (params.forced && !BACKENDS[choice.backend].isAvailable(params.ctx)) {
|
|
136
|
+
return { ok: false, error: `--terminal ${choice.backend} is not available here.` };
|
|
137
|
+
}
|
|
138
|
+
const command = buildRunCommand(stripTerminalFlag(params.argv, params.consumedValue));
|
|
139
|
+
const result = await openSurface({
|
|
140
|
+
backend: choice.backend,
|
|
141
|
+
layout: 'tab',
|
|
142
|
+
cwd: params.cwd,
|
|
143
|
+
command,
|
|
144
|
+
});
|
|
145
|
+
return { ok: result.ok, choice, description: describeBackendChoice(choice), error: result.error };
|
|
146
|
+
}
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
* is attended and live. See docs/terminal-engine.md.
|
|
9
9
|
*/
|
|
10
10
|
/** An interactive terminal backend the engine can drive. */
|
|
11
|
-
export type Backend = 'iterm' | 'ghostty' | 'tmux' | 'vscodium-agent';
|
|
11
|
+
export type Backend = 'iterm' | 'ghostty' | 'tmux' | 'vscodium-agent' | 'terminal';
|
|
12
12
|
/** Which way a split pane grows. `right` = side-by-side; `down` = stacked. */
|
|
13
13
|
export type SplitDirection = 'right' | 'down';
|
|
14
14
|
/** Where a surface lands: a new tab, or a split of the current pane. */
|
package/dist/lib/types.d.ts
CHANGED
|
@@ -62,7 +62,7 @@ export interface BudgetConfig {
|
|
|
62
62
|
require_confirm_over?: number;
|
|
63
63
|
}
|
|
64
64
|
/** Preview features that users can opt into via `agents beta`. */
|
|
65
|
-
export type BetaFeatureName = 'drive' | 'factory' | 'session-sync';
|
|
65
|
+
export type BetaFeatureName = 'drive' | 'factory' | 'session-sync' | 'projects';
|
|
66
66
|
/** Subset of chalk color names used for agent-specific terminal output. */
|
|
67
67
|
export type ChalkColor = 'magenta' | 'green' | 'blue' | 'cyan' | 'yellowBright' | 'redBright' | 'whiteBright' | 'blueBright' | 'greenBright' | 'magentaBright' | 'cyanBright';
|
|
68
68
|
/** Static configuration for a single agent -- paths, capabilities, and format conventions. */
|