@phnx-labs/agents-cli 1.22.46 → 1.22.47
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 +279 -0
- package/LICENSE +102 -182
- package/README.md +31 -18
- package/dist/cli/command-registry.d.ts +1 -1
- package/dist/cli/command-registry.js +4 -2
- package/dist/commands/accounts.js +56 -12
- package/dist/commands/auth.js +7 -5
- package/dist/commands/browser-sessions-picker.js +9 -7
- package/dist/commands/browser.js +266 -15
- package/dist/commands/config.js +26 -4
- package/dist/commands/cost.d.ts +1 -1
- package/dist/commands/detach.d.ts +13 -0
- package/dist/commands/detach.js +11 -8
- package/dist/commands/exec.js +55 -38
- package/dist/commands/feed-watch.d.ts +2 -0
- package/dist/commands/feed-watch.js +28 -0
- package/dist/commands/feed.js +27 -0
- package/dist/commands/feedback.js +3 -19
- package/dist/commands/insights.d.ts +1 -1
- package/dist/commands/insights.js +2 -2
- package/dist/commands/lease.js +3 -3
- package/dist/commands/routines.js +1 -1
- package/dist/commands/sessions-picker-factory.d.ts +2 -1
- package/dist/commands/sessions-picker-factory.js +1 -1
- package/dist/commands/sessions-stop.d.ts +24 -0
- package/dist/commands/sessions-stop.js +66 -0
- package/dist/commands/sessions-trace.d.ts +82 -0
- package/dist/commands/sessions-trace.js +387 -0
- package/dist/commands/sessions.js +4 -0
- package/dist/commands/ssh.d.ts +15 -0
- package/dist/commands/ssh.js +194 -16
- package/dist/commands/teams.js +34 -2
- package/dist/commands/view.d.ts +2 -0
- package/dist/commands/view.js +18 -31
- package/dist/commands/watchdog.js +9 -14
- package/dist/lib/account-capabilities.d.ts +4 -0
- package/dist/lib/account-capabilities.js +13 -9
- package/dist/lib/account-registry.d.ts +2 -0
- package/dist/lib/account-registry.js +30 -2
- package/dist/lib/accounting/rotate.d.ts +9 -0
- package/dist/lib/accounting/rotate.js +12 -2
- package/dist/lib/accounting/usage.d.ts +70 -3
- package/dist/lib/accounting/usage.js +235 -63
- package/dist/lib/agent-modes.js +29 -1
- package/dist/lib/agent-spec/agents.d.ts +4 -3
- package/dist/lib/agent-spec/agents.js +14 -6
- package/dist/lib/analytics/mix-commands.d.ts +1 -1
- package/dist/lib/analytics/mix-commands.js +2 -2
- package/dist/lib/auth-health.d.ts +13 -3
- package/dist/lib/auth-health.js +50 -8
- package/dist/lib/browser/devices.d.ts +19 -0
- package/dist/lib/browser/devices.js +23 -0
- package/dist/lib/browser/ipc.d.ts +1 -1
- package/dist/lib/browser/ipc.js +33 -7
- package/dist/lib/browser/profiles.d.ts +106 -1
- package/dist/lib/browser/profiles.js +300 -17
- package/dist/lib/browser/remote-control.d.ts +26 -2
- package/dist/lib/browser/remote-control.js +33 -3
- package/dist/lib/browser/runtime-state.d.ts +9 -0
- package/dist/lib/browser/runtime-state.js +24 -8
- package/dist/lib/browser/service.d.ts +37 -0
- package/dist/lib/browser/service.js +78 -20
- package/dist/lib/browser/sessions-list.d.ts +1 -3
- package/dist/lib/browser/sessions-list.js +3 -16
- package/dist/lib/browser/types.d.ts +12 -1
- package/dist/lib/codex-policy.d.ts +11 -1
- package/dist/lib/codex-policy.js +20 -5
- package/dist/lib/config-keys.d.ts +4 -3
- package/dist/lib/config-keys.js +17 -7
- package/dist/lib/daemon/runner.js +15 -2
- package/dist/lib/daemon-ticks.js +2 -2
- package/dist/lib/device-config.d.ts +25 -8
- package/dist/lib/device-config.js +90 -17
- package/dist/lib/devices/config-migration.d.ts +7 -0
- package/dist/lib/devices/config-migration.js +67 -3
- package/dist/lib/devices/health.d.ts +27 -2
- package/dist/lib/devices/health.js +39 -9
- package/dist/lib/devices/interactive-host.d.ts +15 -0
- package/dist/lib/devices/interactive-host.js +53 -0
- package/dist/lib/devices/registry.d.ts +61 -4
- package/dist/lib/devices/registry.js +94 -38
- package/dist/lib/devices/stats-cache.d.ts +6 -0
- package/dist/lib/devices/stats-cache.js +10 -2
- package/dist/lib/exec.d.ts +5 -3
- package/dist/lib/exec.js +32 -16
- package/dist/lib/feed/answer.d.ts +29 -0
- package/dist/lib/feed/answer.js +145 -0
- package/dist/lib/feed/attention.d.ts +83 -0
- package/dist/lib/feed/attention.js +0 -0
- package/dist/lib/feed/feed.d.ts +109 -0
- package/dist/lib/feed/feed.js +154 -3
- package/dist/lib/feed/pr-status.d.ts +14 -0
- package/dist/lib/feed/pr-status.js +47 -0
- package/dist/lib/feed/watch.d.ts +59 -0
- package/dist/lib/feed/watch.js +180 -0
- package/dist/lib/fleet/capture.js +6 -0
- package/dist/lib/fleet/remote-login.js +8 -3
- package/dist/lib/fleet/types.d.ts +17 -0
- package/dist/lib/harness/adapter.d.ts +11 -0
- package/dist/lib/harness/adapters/claude.js +38 -24
- package/dist/lib/harness/adapters/codex.js +2 -4
- package/dist/lib/hooks/cache.js +8 -0
- package/dist/lib/hooks/match.d.ts +3 -0
- package/dist/lib/hooks/match.js +25 -0
- package/dist/lib/hosts/passthrough.js +16 -0
- package/dist/lib/hosts/registry.js +7 -0
- package/dist/lib/menubar/MenubarHelper.app/Contents/CodeResources +0 -0
- package/dist/lib/menubar/MenubarHelper.app/Contents/MacOS/MenubarHelper +0 -0
- package/dist/lib/menubar/install-menubar.d.ts +13 -0
- package/dist/lib/menubar/install-menubar.js +26 -2
- package/dist/lib/open-url.d.ts +60 -2
- package/dist/lib/open-url.js +167 -12
- package/dist/lib/refresh-coordinator.d.ts +1 -1
- package/dist/lib/refresh-coordinator.js +1 -1
- 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/session/active.d.ts +67 -0
- package/dist/lib/session/active.js +69 -1
- package/dist/lib/session/db.js +4 -2
- package/dist/lib/session/discover.d.ts +2 -0
- package/dist/lib/session/discover.js +12 -6
- package/dist/lib/session/prompt.d.ts +35 -0
- package/dist/lib/session/prompt.js +56 -0
- package/dist/lib/session/remote/watch.d.ts +1 -1
- package/dist/lib/session/remote/watch.js +6 -1
- package/dist/lib/session/render.js +7 -3
- package/dist/lib/session/session-cache.js +5 -1
- package/dist/lib/session/trajectory-compare.d.ts +47 -0
- package/dist/lib/session/trajectory-compare.js +116 -0
- package/dist/lib/session/trajectory-html.d.ts +15 -0
- package/dist/lib/session/trajectory-html.js +750 -0
- package/dist/lib/session/trajectory-lineage.d.ts +92 -0
- package/dist/lib/session/trajectory-lineage.js +195 -0
- package/dist/lib/session/trajectory-text.d.ts +37 -0
- package/dist/lib/session/trajectory-text.js +277 -0
- package/dist/lib/session/trajectory.d.ts +102 -0
- package/dist/lib/session/trajectory.js +335 -0
- package/dist/lib/startup/command-registry.d.ts +3 -1
- package/dist/lib/startup/command-registry.js +5 -2
- package/dist/lib/state.d.ts +1 -1
- package/dist/lib/state.js +4 -2
- package/dist/lib/teams/agents.js +9 -0
- package/dist/lib/terminal/inject.d.ts +19 -0
- package/dist/lib/terminal/inject.js +19 -9
- package/dist/lib/tmux/session.d.ts +46 -13
- package/dist/lib/tmux/session.js +173 -27
- package/dist/lib/types.d.ts +16 -0
- package/dist/lib/usage-backoff.d.ts +2 -1
- package/dist/lib/usage-backoff.js +60 -19
- package/dist/lib/usage-refresh.d.ts +17 -3
- package/dist/lib/usage-refresh.js +57 -11
- package/dist/lib/watchdog/log.d.ts +1 -1
- package/dist/lib/watchdog/log.js +1 -1
- package/dist/lib/watchdog/runner.d.ts +36 -45
- package/dist/lib/watchdog/runner.js +150 -119
- package/dist/lib/watchdog/service.d.ts +0 -1
- package/dist/lib/watchdog/service.js +0 -1
- package/dist/lib/watchdog/watchdog-agent.d.ts +38 -0
- package/dist/lib/watchdog/watchdog-agent.js +59 -0
- package/dist/lib/watchdog/watchdog.d.ts +11 -2
- package/dist/lib/watchdog/watchdog.js +37 -104
- package/package.json +2 -2
- package/dist/commands/usage.d.ts +0 -27
- package/dist/commands/usage.js +0 -96
package/dist/commands/exec.js
CHANGED
|
@@ -14,6 +14,7 @@ import { isInteractiveTerminal, isPromptCancelled, requireInteractiveSelection }
|
|
|
14
14
|
import { getUserAgentsDir, readMeta } from '../lib/state.js';
|
|
15
15
|
import { parseLoopInterval } from '../lib/loop.js';
|
|
16
16
|
import { AGENTS, resolveAgentName, isAgentHardDeprecated, hardDeprecationError } from '../lib/agents.js';
|
|
17
|
+
import { parseAgentVersionSpec } from '../lib/agent-spec/agents.js';
|
|
17
18
|
import { recordDispatchedRun } from '../lib/audit/log.js';
|
|
18
19
|
import { maybeShowStarNudge } from '../lib/star-nudge.js';
|
|
19
20
|
import { warnUnpushedWork, shouldWarnUnpushed } from '../lib/warn-unpushed.js';
|
|
@@ -416,7 +417,7 @@ async function handleTerminalHandoff(agentSpec, options, prompt) {
|
|
|
416
417
|
// isValidAgent / profileExists / resolveWorkflowRef chain below), so this must
|
|
417
418
|
// accept all three. Gating on the agent table alone rejected every profile —
|
|
418
419
|
// the whole Kimi/DeepSeek/Qwen/GLM path — for `--terminal` runs only.
|
|
419
|
-
const rawTarget = parseRunAccountPickerRequest(agentSpec).normalizedAgentSpec.split('@')[0];
|
|
420
|
+
const rawTarget = parseRunAccountPickerRequest(agentSpec).normalizedAgentSpec.split('#')[0].split('@')[0];
|
|
420
421
|
const knownAgent = resolveAgentName(rawTarget);
|
|
421
422
|
const [{ profileExists }, { resolveWorkflowRef }] = await Promise.all([
|
|
422
423
|
import('../lib/profiles.js'),
|
|
@@ -490,7 +491,7 @@ export function registerRunCommand(program) {
|
|
|
490
491
|
const runCmd = program
|
|
491
492
|
.command('run [agent] [prompt]')
|
|
492
493
|
.description('Execute an agent. Pass a prompt for headless runs; omit it to launch the agent interactively. With --broadcast, run the same prompt/task across an agent × model matrix.')
|
|
493
|
-
.option('-m, --mode <mode>', 'How much the agent can do: plan (read-only), edit (can write files), auto (smart classifier auto-approves safe ops
|
|
494
|
+
.option('-m, --mode <mode>', 'How much the agent can do: plan (read-only), edit (can write files), auto (more autonomous than edit, mechanism per-harness: smart classifier auto-approves safe ops and still prompts for risky ones on Claude/Copilot; approval_policy=never over the edit sandbox on Codex, which never prompts), skip (bypass all permission prompts). Omitted Codex mode defaults to safe writable edit; other harnesses default to plan. \'full\' accepted as alias for skip.', 'plan')
|
|
494
495
|
.option('-e, --effort <effort>', 'Reasoning effort: low | medium | high | xhigh | max | auto (claude and codex only)', 'auto')
|
|
495
496
|
.option('--model <model>', 'Cost tier (cheap|default|best|ultra) or a concrete model id; tiers resolve per harness+version to a supported model')
|
|
496
497
|
.option('--env <key=value>', 'Pass environment variable to the agent (repeatable, e.g., --env DEBUG=1 --env API_KEY=xyz)', (val, prev) => [...prev, val], [])
|
|
@@ -517,14 +518,14 @@ export function registerRunCommand(program) {
|
|
|
517
518
|
.option('--notify', 'Post a desktop notification when a headless run finishes. Fired by this process on exit, so it survives whatever launched the run (the menu bar dispatching it, a terminal you closed).')
|
|
518
519
|
.option('--terminal [backend]', "Open this run in a real terminal tab instead of here. Without a value the terminal is detected from your live sessions (`agents sessions --active` host), so it lands where you already work — Ghostty for a Ghostty user, iTerm for an iTerm user. Name one to force it: iterm | ghostty | terminal | tmux | vscodium-agent. This is how the menu bar's New Session opens.")
|
|
519
520
|
.option('--verbose', 'Show detailed execution logs')
|
|
520
|
-
.option('--raw', '
|
|
521
|
-
.option('--no-tmux', '
|
|
522
|
-
.option('--disable-tmux', '
|
|
521
|
+
.option('--raw', 'Keep this interactive run direct when the device has opted into tmux wrapping. A no-op under the default tmux-off configuration; equivalent to AGENTS_NO_TMUX=1.')
|
|
522
|
+
.option('--no-tmux', 'Keep this run direct when tmux wrapping is enabled for the device. Same effect as --raw / AGENTS_NO_TMUX=1; it is a no-op under the default tmux-off configuration.')
|
|
523
|
+
.option('--disable-tmux', 'Compatibility alias for --no-tmux; a no-op when tmux wrapping is already off.')
|
|
523
524
|
.option('--timeout <duration>', 'Kill the agent after this duration (e.g., 30m, 1h, 2h30m)')
|
|
524
525
|
.option('--fallback <agents>', 'Comma-separated agents to try on rate-limit failure. Each entry accepts an optional @version pin (e.g., codex@0.116.0,antigravity). The primary runs first; if it exits with a rate-limit error, the next agent picks up via /continue handoff.')
|
|
525
526
|
.option('-b, --balanced', 'Shortcut for --strategy balanced. Ignored when @version is pinned.')
|
|
526
527
|
.option('--strategy <strategy>', 'Version/account selection strategy: pinned | available | balanced. Defaults to run.<agent>.strategy, then balanced (spreads load across healthy accounts and skips any that are rate-limited). (Legacy `rotate` accepted as alias for `balanced`.)')
|
|
527
|
-
.option('--account <
|
|
528
|
+
.option('--account <label>', 'Use this labeled native login or durable provider credential for the run')
|
|
528
529
|
.option('--acp', 'Route through the Agent Client Protocol instead of direct exec. Supported for claude via @zed-industries/claude-code-acp adapter. Unified event stream; emits ndjson when --json.')
|
|
529
530
|
.option('-y, --yes', 'Skip the interactive budget-confirm prompt (require_confirm_over). Never skips a hard budget block.', false)
|
|
530
531
|
.option('--loop', 'Re-inject the prompt/entrypoint each iteration until a stop condition (issue #332). Guards (--max-iterations, --budget, --until) are enforced outside the agent. Writes a checkpoint after every iteration for --resume-checkpoint.')
|
|
@@ -534,7 +535,7 @@ export function registerRunCommand(program) {
|
|
|
534
535
|
.option('--until <signal>', 'Loop stop condition. `signal` reads <runDir>/loop-signal.json {continue,reason} each iteration; absent or continue:false stops (fail-closed). Loop only.')
|
|
535
536
|
.option('--interval <dur>', 'Loop delay between iterations ("0" back-to-back, "30m" paces). Loop only.')
|
|
536
537
|
.option('--where <spec>', 'Where this run\'s body executes (one placement door): local | device:<name> | auto | lease[:backend] | cloud[:provider]. Expands to --device/--lease/--cloud. Do not combine with those flags. See docs/00-concepts.md#placement.')
|
|
537
|
-
.option('-D, --device <name>', 'Offload this run onto another machine over SSH — a registered device, or user@host. Pass "auto" to pick the least-loaded reachable device where the requested agent is installed and signed in, keeping the run local when no remote is better. Same as --where device:<name>. See `agents devices`.')
|
|
538
|
+
.option('-D, --device <name>', 'Offload this run onto another machine over SSH — a registered device, or user@host. Pass "auto" to pick the least-loaded reachable device where the requested agent is installed and signed in, keeping the run local when no remote is better, or "interactive" for the machine pinned as interactive.host (the box a human is sitting at). Same as --where device:<name>. See `agents devices`.')
|
|
538
539
|
.option('--remote-cwd <dir>', "Explicit device working directory for --device runs, used VERBATIM (overrides --cwd; usually --cwd suffices — it re-roots a local-home path onto the remote home). Pass a single-quoted '$HOME/…' or a valid remote absolute path; a local ~ expands here and won't exist there (/Users/you vs /home/you).")
|
|
539
540
|
.option('--no-follow', 'With --device, dispatch detached and return immediately (track via `agents hosts ps/logs`).')
|
|
540
541
|
.option('--any', 'With --device <cap> (a capability tag), pick any matching device instead of erroring when several match.')
|
|
@@ -636,7 +637,11 @@ agents run auto --device yosemite-s0 "fix the flaky test" # pin the device
|
|
|
636
637
|
Modes (not every agent supports every mode — run \`agents modes <agent>\`):
|
|
637
638
|
plan read-only investigation; no writes, no shell side-effects
|
|
638
639
|
edit may edit files; prompts for shell / risky operations
|
|
639
|
-
auto
|
|
640
|
+
auto more autonomous than edit; the mechanism is per-harness --
|
|
641
|
+
claude, copilot: smart classifier auto-approves safe ops and
|
|
642
|
+
STILL PROMPTS for risky ones
|
|
643
|
+
codex: approval_policy=never over the edit sandbox; never
|
|
644
|
+
prompts at all, and a denied command fails instead
|
|
640
645
|
skip bypass every permission prompt (dangerously-skip-permissions)
|
|
641
646
|
Legacy 'full' is silently rewritten to 'skip'.
|
|
642
647
|
List per-harness support + native flags: agents modes · agents modes claude
|
|
@@ -829,7 +834,7 @@ agents run auto --device yosemite-s0 "fix the flaky test" # pin the device
|
|
|
829
834
|
process.exit(1);
|
|
830
835
|
}
|
|
831
836
|
// Hard-deprecated harnesses cannot be run — point the user at the successor.
|
|
832
|
-
const runBaseAgentName = normalizedAgentSpec.split('@')[0];
|
|
837
|
+
const runBaseAgentName = normalizedAgentSpec.split('#')[0].split('@')[0];
|
|
833
838
|
const runBaseAgentId = resolveAgentName(runBaseAgentName);
|
|
834
839
|
const { profileExists: runProfileExists } = await import('../lib/profiles.js');
|
|
835
840
|
if (runBaseAgentId && !runProfileExists(runBaseAgentName) && isAgentHardDeprecated(runBaseAgentId)) {
|
|
@@ -849,7 +854,7 @@ agents run auto --device yosemite-s0 "fix the flaky test" # pin the device
|
|
|
849
854
|
}
|
|
850
855
|
// `agents run auto`: the reserved harness keyword — full-auto dispatch
|
|
851
856
|
// (host affinity → cross-harness balance → account balance, RUSH-2132).
|
|
852
|
-
if (normalizedAgentSpec.split('@')[0] === RUN_AUTO_KEYWORD && normalizedAgentSpec !== RUN_AUTO_KEYWORD) {
|
|
857
|
+
if (normalizedAgentSpec.split('#')[0].split('@')[0] === RUN_AUTO_KEYWORD && normalizedAgentSpec !== RUN_AUTO_KEYWORD) {
|
|
853
858
|
console.error(chalk.red(`agents run auto picks the harness itself — a @version pin does not apply. ` +
|
|
854
859
|
`Pin a concrete harness instead: agents run <harness>@<version>.`));
|
|
855
860
|
process.exit(1);
|
|
@@ -896,7 +901,7 @@ agents run auto --device yosemite-s0 "fix the flaky test" # pin the device
|
|
|
896
901
|
process.exit(1);
|
|
897
902
|
}
|
|
898
903
|
resolvedResumeSource = outcome.session;
|
|
899
|
-
const [requestedAgent, requestedVersion] = normalizedAgentSpec.split('@');
|
|
904
|
+
const [requestedAgent, requestedVersion] = normalizedAgentSpec.split('#')[0].split('@');
|
|
900
905
|
if (!autoHarnessRequested && requestedAgent !== resolvedResumeSource.agent) {
|
|
901
906
|
console.error(chalk.red(`Session ${resolvedResumeSource.shortId} belongs to ${resolvedResumeSource.agent}, not ${requestedAgent}. ` +
|
|
902
907
|
`Use: agents sessions resume ${resolvedResumeSource.id}`));
|
|
@@ -982,9 +987,9 @@ agents run auto --device yosemite-s0 "fix the flaky test" # pin the device
|
|
|
982
987
|
accountPickerRequested,
|
|
983
988
|
// `run auto` selects its harness after placement, so do not filter
|
|
984
989
|
// candidates against an arbitrary proxy harness at this stage.
|
|
985
|
-
agent: normalizedAgentSpec.split('@')[0] === RUN_AUTO_KEYWORD
|
|
990
|
+
agent: normalizedAgentSpec.split('#')[0].split('@')[0] === RUN_AUTO_KEYWORD
|
|
986
991
|
? undefined
|
|
987
|
-
: (resolveAgentName(normalizedAgentSpec.split('@')[0]) ?? undefined),
|
|
992
|
+
: (resolveAgentName(normalizedAgentSpec.split('#')[0].split('@')[0]) ?? undefined),
|
|
988
993
|
});
|
|
989
994
|
if (!options.quiet && result.deprecationSmart) {
|
|
990
995
|
process.stderr.write(chalk.yellow('[agents] --smart is deprecated; use --device auto\n'));
|
|
@@ -1137,7 +1142,7 @@ agents run auto --device yosemite-s0 "fix the flaky test" # pin the device
|
|
|
1137
1142
|
const { getConfiguredRunStrategy, resolveRunVersion } = await import('../lib/accounting/rotate.js');
|
|
1138
1143
|
const { profileExists, readProfile, resolveProfileEnv } = await import('../lib/profiles.js');
|
|
1139
1144
|
const detected = await detectSignedInRuntimes();
|
|
1140
|
-
const [agentName, rawLeaseVersion] = normalizedAgentSpec.split('@');
|
|
1145
|
+
const [agentName, rawLeaseVersion] = normalizedAgentSpec.split('#')[0].split('@');
|
|
1141
1146
|
let runtime = null;
|
|
1142
1147
|
let credentialRuntimes = [];
|
|
1143
1148
|
let dispatchProfile;
|
|
@@ -1398,7 +1403,7 @@ agents run auto --device yosemite-s0 "fix the flaky test" # pin the device
|
|
|
1398
1403
|
throw e;
|
|
1399
1404
|
}
|
|
1400
1405
|
try {
|
|
1401
|
-
const [runAgent, rawRunVersion] = normalizedAgentSpec.split('@');
|
|
1406
|
+
const [runAgent, rawRunVersion] = normalizedAgentSpec.split('#')[0].split('@');
|
|
1402
1407
|
// Forward the explicit @version pin verbatim. Resolving aliases like
|
|
1403
1408
|
// @latest locally would check local installs, but the remote host may
|
|
1404
1409
|
// have versions the laptop does not. The remote agents CLI resolves
|
|
@@ -1754,12 +1759,33 @@ agents run auto --device yosemite-s0 "fix the flaky test" # pin the device
|
|
|
1754
1759
|
import('../lib/share/config.js'),
|
|
1755
1760
|
]);
|
|
1756
1761
|
const isValidAgent = (agent) => ALL_AGENT_IDS.includes(agent);
|
|
1757
|
-
// Parse agent@version
|
|
1758
|
-
|
|
1762
|
+
// Parse agent@version#label. The label selects native auth without pinning
|
|
1763
|
+
// the binary version; --account remains the equivalent flag form.
|
|
1764
|
+
const labelParts = normalizedAgentSpec.split('#');
|
|
1765
|
+
if (labelParts.length > 2 || labelParts[1] === '') {
|
|
1766
|
+
console.error(chalk.red(`Invalid account label in '${normalizedAgentSpec}'.`));
|
|
1767
|
+
process.exit(1);
|
|
1768
|
+
}
|
|
1769
|
+
const [rawAgent, rawVersion] = labelParts[0].split('@');
|
|
1770
|
+
const specAccountLabel = labelParts[1];
|
|
1771
|
+
if (resolveAgentName(rawAgent)) {
|
|
1772
|
+
const parsed = parseAgentVersionSpec(normalizedAgentSpec);
|
|
1773
|
+
if ('error' in parsed) {
|
|
1774
|
+
console.error(chalk.red(parsed.error));
|
|
1775
|
+
process.exit(1);
|
|
1776
|
+
}
|
|
1777
|
+
}
|
|
1778
|
+
if (specAccountLabel && options.account && specAccountLabel !== options.account) {
|
|
1779
|
+
console.error(chalk.red(`Account '${specAccountLabel}' from the agent spec conflicts with --account '${options.account}'.`));
|
|
1780
|
+
process.exit(1);
|
|
1781
|
+
}
|
|
1782
|
+
if (specAccountLabel)
|
|
1783
|
+
options.account = specAccountLabel;
|
|
1759
1784
|
let agent;
|
|
1760
1785
|
let version = rawVersion || undefined;
|
|
1761
1786
|
let profileEnv;
|
|
1762
1787
|
let accountEnv;
|
|
1788
|
+
let accountConfigVersion;
|
|
1763
1789
|
let profileProvider;
|
|
1764
1790
|
let fromProfile = false;
|
|
1765
1791
|
let profileFallbackModel;
|
|
@@ -2126,29 +2152,19 @@ agents run auto --device yosemite-s0 "fix the flaky test" # pin the device
|
|
|
2126
2152
|
console.error(chalk.red(`Account '${spawnAccount.name}' is a device-local ${spawnAccount.agent} login and cannot be forwarded to '${remoteTarget}'. Sign in on that device and name the login there.`));
|
|
2127
2153
|
process.exit(1);
|
|
2128
2154
|
}
|
|
2129
|
-
|
|
2130
|
-
|
|
2131
|
-
|
|
2132
|
-
|
|
2133
|
-
const liveKey = info.accountKey ?? info.email?.toLowerCase() ?? null;
|
|
2134
|
-
if (!info.signedIn || liveKey !== spawnAccount.identityKey) {
|
|
2135
|
-
console.error(chalk.red(`Account '${spawnAccount.name}' names a specific ${spawnAccount.agent} identity, but ${agent}@${version} ${info.signedIn ? 'is signed in as a different identity' : 'is not signed in'}. Sign in as that identity, or re-name the account.`));
|
|
2136
|
-
process.exit(1);
|
|
2137
|
-
}
|
|
2155
|
+
const { CONFIG_ENV_ISOLATED_AGENTS } = await import('../lib/installations/shims.js');
|
|
2156
|
+
if (!CONFIG_ENV_ISOLATED_AGENTS.includes(agent)) {
|
|
2157
|
+
console.error(chalk.red(`${agent} cannot select native accounts independently of its installed version.`));
|
|
2158
|
+
process.exit(1);
|
|
2138
2159
|
}
|
|
2139
|
-
|
|
2140
|
-
|
|
2141
|
-
|
|
2142
|
-
|
|
2143
|
-
|
|
2144
|
-
const matched = await resolveAccountVersion(agent, spawnAccount.identityKey);
|
|
2145
|
-
if (!matched) {
|
|
2146
|
-
console.error(chalk.red(`No installed ${spawnAccount.agent} version is signed in as the identity named by account '${spawnAccount.name}'. Sign in as that identity, or attach a different account.`));
|
|
2147
|
-
process.exit(1);
|
|
2148
|
-
}
|
|
2149
|
-
version = matched;
|
|
2160
|
+
const { resolveAccountVersion } = await import('../lib/accounting/rotate.js');
|
|
2161
|
+
accountConfigVersion = await resolveAccountVersion(agent, spawnAccount.identityKey) ?? undefined;
|
|
2162
|
+
if (!accountConfigVersion) {
|
|
2163
|
+
console.error(chalk.red(`No installed ${spawnAccount.agent} version is signed in as the identity labeled '${spawnAccount.name}'. Sign in as that identity, or label a different account.`));
|
|
2164
|
+
process.exit(1);
|
|
2150
2165
|
}
|
|
2151
|
-
|
|
2166
|
+
if (!options.quiet)
|
|
2167
|
+
process.stderr.write(chalk.gray(`[agents] account '${spawnAccount.name}' · ${agent} auth from ${accountConfigVersion}\n`));
|
|
2152
2168
|
}
|
|
2153
2169
|
else {
|
|
2154
2170
|
accountEnv = spawnAccount.env;
|
|
@@ -2670,6 +2686,7 @@ agents run auto --device yosemite-s0 "fix the flaky test" # pin the device
|
|
|
2670
2686
|
const execOptions = {
|
|
2671
2687
|
agent,
|
|
2672
2688
|
version,
|
|
2689
|
+
configVersion: accountConfigVersion,
|
|
2673
2690
|
prompt,
|
|
2674
2691
|
interactive: options.interactive || forceInteractive,
|
|
2675
2692
|
mode: requestedMode,
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { machineId } from '../lib/machine-id.js';
|
|
2
|
+
import { setHelpSections } from '../lib/help.js';
|
|
3
|
+
import { watchFleetFeed, watchLocalFeed } from '../lib/feed/watch.js';
|
|
4
|
+
export function registerFeedWatchCommand(parent) {
|
|
5
|
+
const command = parent.command('watch').description('Stream the canonical agent, attention, and activity projection as NDJSON')
|
|
6
|
+
.option('--json', 'Emit versioned NDJSON envelopes').option('--local', 'Watch only this machine');
|
|
7
|
+
setHelpSections(command, { examples: `agents feed watch --json\nagents feed watch --json --local`, notes: 'Order by streamId + sequence. Unavailable scopes retain their last rows until reset on reconnect.' });
|
|
8
|
+
command.action(async (_opts, invoked) => {
|
|
9
|
+
const opts = invoked.optsWithGlobals();
|
|
10
|
+
if (!opts.json)
|
|
11
|
+
invoked.error("error: required option '--json' not specified");
|
|
12
|
+
const controller = new AbortController();
|
|
13
|
+
const stop = () => controller.abort();
|
|
14
|
+
const emit = (event) => process.stdout.write(`${JSON.stringify(event)}\n`);
|
|
15
|
+
process.once('SIGINT', stop);
|
|
16
|
+
process.once('SIGTERM', stop);
|
|
17
|
+
try {
|
|
18
|
+
if (opts.local)
|
|
19
|
+
await watchLocalFeed({ scope: machineId(), signal: controller.signal, emit });
|
|
20
|
+
else
|
|
21
|
+
await watchFleetFeed({ signal: controller.signal, emit });
|
|
22
|
+
}
|
|
23
|
+
finally {
|
|
24
|
+
process.off('SIGINT', stop);
|
|
25
|
+
process.off('SIGTERM', stop);
|
|
26
|
+
}
|
|
27
|
+
});
|
|
28
|
+
}
|
package/dist/commands/feed.js
CHANGED
|
@@ -13,6 +13,8 @@ import { relTime } from '../lib/format.js';
|
|
|
13
13
|
import { gatherRemoteAgentsJson } from '../lib/remote-agents-json.js';
|
|
14
14
|
import { loadPolicy, applyPolicyToBlock, isPhoneUrgent } from '../lib/feed-policy.js';
|
|
15
15
|
import { notifyUrgentBlock } from '../lib/notify.js';
|
|
16
|
+
import { registerFeedWatchCommand } from './feed-watch.js';
|
|
17
|
+
import { claimAndRouteAttentionAnswer, forwardFeedAnswer } from '../lib/feed/answer.js';
|
|
16
18
|
import { gcMailbox } from '../lib/mailbox-gc.js';
|
|
17
19
|
import { isValidMailboxId } from '../lib/mailbox.js';
|
|
18
20
|
import { getActiveSessions } from '../lib/session/active.js';
|
|
@@ -323,6 +325,31 @@ export function registerFeedCommand(program) {
|
|
|
323
325
|
.option('--dispatch', 'Run stall suppression + default-on-no-answer policy and urgent notifications')
|
|
324
326
|
.option('--pause <id>', 'Pause a runaway/needy local process (SIGSTOP) or cancel a cloud task')
|
|
325
327
|
.option('--kill <id>', 'Kill a runaway/needy local process (SIGTERM) or cancel a cloud task');
|
|
328
|
+
registerFeedWatchCommand(feed);
|
|
329
|
+
feed.command('answer <attention-key>')
|
|
330
|
+
.description('Atomically claim and deliver one answer to an open attention item')
|
|
331
|
+
.option('--choice <choice-id>', 'Stable choice id from the attention item')
|
|
332
|
+
.option('--text <answer>', 'Free-text answer')
|
|
333
|
+
.option('--as <operator>', 'Verified operator id for high-consequence answers')
|
|
334
|
+
.option('--json', 'Emit the delivery result as JSON')
|
|
335
|
+
.action(async (attentionKey, opts, invoked) => {
|
|
336
|
+
try {
|
|
337
|
+
const ownerHost = attentionKey.slice(0, attentionKey.indexOf('/'));
|
|
338
|
+
const result = ownerHost && ownerHost !== machineId()
|
|
339
|
+
? await forwardFeedAnswer({ host: ownerHost, attentionKey, choiceId: opts.choice, text: opts.text, operatorId: opts.as })
|
|
340
|
+
: await claimAndRouteAttentionAnswer({
|
|
341
|
+
attentionKey, choiceId: opts.choice, text: opts.text,
|
|
342
|
+
operator: { id: opts.as, verified: Boolean(opts.as), label: opts.as },
|
|
343
|
+
});
|
|
344
|
+
if (opts.json || invoked.parent?.opts()?.json)
|
|
345
|
+
console.log(JSON.stringify(result));
|
|
346
|
+
else
|
|
347
|
+
console.log(result.status === 'delivered' ? `Delivered ${result.receipt.msgId}.` : `Already answered (${result.receipt.at}).`);
|
|
348
|
+
}
|
|
349
|
+
catch (error) {
|
|
350
|
+
invoked.error(`error: ${error instanceof Error ? error.message : String(error)}`);
|
|
351
|
+
}
|
|
352
|
+
});
|
|
326
353
|
feed
|
|
327
354
|
.command('post')
|
|
328
355
|
.description('Post a status update to the fleet activity stream (for agents)')
|
|
@@ -3,10 +3,10 @@
|
|
|
3
3
|
* pre-filled with version + OS + agent inventory; falls back to printing the
|
|
4
4
|
* URL when no browser is available.
|
|
5
5
|
*/
|
|
6
|
-
import { spawnSync } from 'node:child_process';
|
|
7
6
|
import { arch, platform, release } from 'node:os';
|
|
8
7
|
import { createRequire } from 'node:module';
|
|
9
8
|
import chalk from 'chalk';
|
|
9
|
+
import { showUrl } from '../lib/open-url.js';
|
|
10
10
|
const REPO = 'phnx-labs/agents-cli';
|
|
11
11
|
const DISCUSSION_BASE = `https://github.com/${REPO}/discussions/new`;
|
|
12
12
|
const ISSUE_BASE = `https://github.com/${REPO}/issues/new`;
|
|
@@ -20,22 +20,6 @@ function readCliVersion() {
|
|
|
20
20
|
return 'unknown';
|
|
21
21
|
}
|
|
22
22
|
}
|
|
23
|
-
function openInBrowser(url) {
|
|
24
|
-
const openers = process.platform === 'darwin'
|
|
25
|
-
? [['open', [url]]]
|
|
26
|
-
: process.platform === 'win32'
|
|
27
|
-
? [['cmd', ['/c', 'start', '""', url]]]
|
|
28
|
-
: [
|
|
29
|
-
['xdg-open', [url]],
|
|
30
|
-
['gnome-open', [url]]
|
|
31
|
-
];
|
|
32
|
-
for (const [cmd, args] of openers) {
|
|
33
|
-
const r = spawnSync(cmd, args, { stdio: 'ignore' });
|
|
34
|
-
if (r.status === 0)
|
|
35
|
-
return true;
|
|
36
|
-
}
|
|
37
|
-
return false;
|
|
38
|
-
}
|
|
39
23
|
function buildPrefill(kind, summary) {
|
|
40
24
|
const version = readCliVersion();
|
|
41
25
|
const os = `${platform()} ${release()} (${arch()})`;
|
|
@@ -69,7 +53,7 @@ export function registerFeedbackCommand(program) {
|
|
|
69
53
|
.option('-i, --idea', 'File as a feature idea (Discussions → Ideas)')
|
|
70
54
|
.option('-q, --question', 'Ask a question (Discussions → Q&A)')
|
|
71
55
|
.option('--print', 'Print the URL instead of opening it')
|
|
72
|
-
.action((summary, opts) => {
|
|
56
|
+
.action(async (summary, opts) => {
|
|
73
57
|
const kind = opts.bug ? 'bug' : opts.idea ? 'idea' : 'question';
|
|
74
58
|
const summaryText = (summary ?? []).join(' ').trim();
|
|
75
59
|
const { url } = buildPrefill(kind, summaryText);
|
|
@@ -77,7 +61,7 @@ export function registerFeedbackCommand(program) {
|
|
|
77
61
|
console.log(url);
|
|
78
62
|
return;
|
|
79
63
|
}
|
|
80
|
-
const opened =
|
|
64
|
+
const opened = (await showUrl(url)).via !== 'none';
|
|
81
65
|
if (opened) {
|
|
82
66
|
console.log(chalk.dim(`Opened ${kind} form in your browser:\n ${url}`));
|
|
83
67
|
}
|
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
*
|
|
15
15
|
* agents insights cost what you spent ($ and duration)
|
|
16
16
|
* agents insights output what shipped (burn vs PRs and commits)
|
|
17
|
-
* agents
|
|
17
|
+
* agents view live quota headroom (per account, with auth state)
|
|
18
18
|
* agents perf latency (hooks, CLI commands, agent.run) — not popularity
|
|
19
19
|
* agents sessions stats which skills/slash-commands were explicitly invoked
|
|
20
20
|
*
|
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
*
|
|
15
15
|
* agents insights cost what you spent ($ and duration)
|
|
16
16
|
* agents insights output what shipped (burn vs PRs and commits)
|
|
17
|
-
* agents
|
|
17
|
+
* agents view live quota headroom (per account, with auth state)
|
|
18
18
|
* agents perf latency (hooks, CLI commands, agent.run) — not popularity
|
|
19
19
|
* agents sessions stats which skills/slash-commands were explicitly invoked
|
|
20
20
|
*
|
|
@@ -577,7 +577,7 @@ function configureInsightsCommand(cmd) {
|
|
|
577
577
|
Two paths under one verb:
|
|
578
578
|
bare \`agents insights\` — transcript behaviour (tools, friction, rhythm, by account)
|
|
579
579
|
\`agents insights mix\` — cheap counters from sessions.db + usage.db
|
|
580
|
-
Latency is \`agents perf\` (not mix). Quota is \`agents
|
|
580
|
+
Latency is \`agents perf\` (not mix). Quota is \`agents view\`. Spend is
|
|
581
581
|
\`agents insights cost\`; shipped output is \`agents insights output\`. Skill/slash popularity
|
|
582
582
|
is \`agents sessions stats\`. Former top-level \`agents trends\` is \`agents insights mix\`
|
|
583
583
|
(also \`agents insights trends\`).
|
package/dist/commands/lease.js
CHANGED
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
* so a box a concurrent run just reused is never stopped.
|
|
9
9
|
*/
|
|
10
10
|
import chalk from 'chalk';
|
|
11
|
-
import {
|
|
11
|
+
import { showUrl } from '../lib/open-url.js';
|
|
12
12
|
import { crabboxList, crabboxStop, reapSafeOrphans, reapOrphans, setLeaseSecretsBundle } from '../lib/crabbox/cli.js';
|
|
13
13
|
import { isInteractiveTerminal, isPromptCancelled } from './utils.js';
|
|
14
14
|
import { bundleExists, readBundle, writeBundle, keychainRef, bundleItemStore } from '../lib/secrets/bundles.js';
|
|
@@ -94,7 +94,7 @@ async function captureTailscaleAuthKey() {
|
|
|
94
94
|
const { password } = await import('@inquirer/prompts');
|
|
95
95
|
let key;
|
|
96
96
|
try {
|
|
97
|
-
|
|
97
|
+
await showUrl(TAILSCALE_KEYS_URL);
|
|
98
98
|
key = (await password({ message: 'Paste a Tailscale auth key (blank to skip):', mask: true })).trim();
|
|
99
99
|
}
|
|
100
100
|
catch (e) {
|
|
@@ -155,7 +155,7 @@ export async function runLeaseSetup(opts = {}) {
|
|
|
155
155
|
console.error(chalk.bold('\nSet up leasing (Hetzner) — one time (~30s):'));
|
|
156
156
|
console.error(chalk.dim('Opening the Hetzner console. Create/select a project, then Security → API Tokens →'));
|
|
157
157
|
console.error(chalk.dim('Generate a token with Read & Write permission, and copy it.\n'));
|
|
158
|
-
|
|
158
|
+
await showUrl(HETZNER_CONSOLE_URL);
|
|
159
159
|
const { password } = await import('@inquirer/prompts');
|
|
160
160
|
const ora = (await import('ora')).default;
|
|
161
161
|
try {
|
|
@@ -904,7 +904,7 @@ export function registerRoutinesCommands(program) {
|
|
|
904
904
|
.option('--workflow <name>', 'Run an installed workflow (~/.agents/workflows/<name>) via `agents run`. Mutually exclusive with --agent.')
|
|
905
905
|
.option('--command <sh>', 'Run a plain shell command directly (no agent, no auth, no sandbox) — for deterministic housekeeping routines. Mutually exclusive with --agent and --workflow; --prompt is not used.')
|
|
906
906
|
.option('-p, --prompt <prompt>', 'Task instruction for the agent')
|
|
907
|
-
.option('-m, --mode <mode>', "Execution mode: plan (read-only), edit (can write files), auto (
|
|
907
|
+
.option('-m, --mode <mode>', "Execution mode: plan (read-only), edit (can write files), auto (the default; more autonomous than edit, mechanism per-harness — see `agents modes <agent>`), or skip (bypass all permission prompts). 'full' accepted as alias for skip.", 'auto')
|
|
908
908
|
.option('-e, --effort <effort>', 'Reasoning effort: low | medium | high | xhigh | max | auto', 'auto')
|
|
909
909
|
.option('-t, --timeout <timeout>', 'Kill the agent if it runs longer than this (e.g., 10m, 2h, 3d, 1w; max 1w)', '10m')
|
|
910
910
|
.option('--timezone <tz>', 'Interpret schedule in this timezone (e.g., America/Los_Angeles)')
|
|
@@ -11,7 +11,8 @@ export interface SessionsPickerCommandSpec<TRow, TOpts extends SessionsPickerGat
|
|
|
11
11
|
* selector) falls through to the flat printer. Computer has no `--open`.
|
|
12
12
|
*/
|
|
13
13
|
requireOpenUndefined?: boolean;
|
|
14
|
-
|
|
14
|
+
/** May be async — the browser spec's flat path awaits an artifact open. */
|
|
15
|
+
runFlat: (opts: TOpts) => void | Promise<void>;
|
|
15
16
|
buildRows: (opts: TOpts) => TRow[];
|
|
16
17
|
emptyMessage: (opts: TOpts) => string;
|
|
17
18
|
message: string;
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `agents sessions stop <id>` — end a live agent session outright: stop its
|
|
3
|
+
* interactive process and tear down its tmux/mux session, so the session becomes
|
|
4
|
+
* done/closed rather than an orphaned idle background process.
|
|
5
|
+
*
|
|
6
|
+
* The lifecycle sibling of `detach` that does NOT resume headless. `detach`
|
|
7
|
+
* backgrounds a still-wanted agent (stop the terminal, keep it working); `stop`
|
|
8
|
+
* is for when the work is over — the agent's editor tab was closed, or you want
|
|
9
|
+
* it gone. Both reuse the same live-session resolution and the same
|
|
10
|
+
* `stopInteractive` teardown (tmux `kill-session`, else SIGTERM→SIGKILL the pid),
|
|
11
|
+
* so a session killed here reaps its helpers exactly as `detach` does before it
|
|
12
|
+
* resumes.
|
|
13
|
+
*
|
|
14
|
+
* The primary caller is AGI EXT: when a user genuinely closes an agent tab
|
|
15
|
+
* (Cmd+W), the extension runs this so the underlying agent + its mux are shut
|
|
16
|
+
* down instead of being left running detached (the "Cmd+W orphans an idle
|
|
17
|
+
* session" bug). A window RELOAD does NOT call this — the extension distinguishes
|
|
18
|
+
* the two via `terminal.exitStatus.reason` and only a real user close tears down.
|
|
19
|
+
*/
|
|
20
|
+
import type { Command } from 'commander';
|
|
21
|
+
export declare function registerSessionsStopCommand(program: Command): void;
|
|
22
|
+
export declare function stopSessionAction(id: string, opts?: {
|
|
23
|
+
local?: boolean;
|
|
24
|
+
}): Promise<void>;
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import chalk from 'chalk';
|
|
2
|
+
import { gatherLiveTargets } from './go.js';
|
|
3
|
+
import { resolveDetachTarget, resolveOne } from './detach-core.js';
|
|
4
|
+
import { stopInteractive } from './detach.js';
|
|
5
|
+
import { runOnPeer } from '../lib/session/remote-list.js';
|
|
6
|
+
import { setHelpSections } from '../lib/help.js';
|
|
7
|
+
export function registerSessionsStopCommand(program) {
|
|
8
|
+
const cmd = program
|
|
9
|
+
.command('stop')
|
|
10
|
+
.argument('<id>', 'Short or full id of the live session to stop')
|
|
11
|
+
.option('--local', 'Only this machine (skip the cross-host sweep)')
|
|
12
|
+
.description('Stop a live agent outright — end its process and tear down its tmux/mux session')
|
|
13
|
+
.action(async (id, opts) => {
|
|
14
|
+
await stopSessionAction(id, opts);
|
|
15
|
+
});
|
|
16
|
+
setHelpSections(cmd, {
|
|
17
|
+
examples: `
|
|
18
|
+
# Stop a live session by a short id prefix
|
|
19
|
+
agents sessions stop 4b2f1a9c
|
|
20
|
+
|
|
21
|
+
# Only look on this machine (skip the fleet sweep)
|
|
22
|
+
agents sessions stop 4b2f1a9c --local
|
|
23
|
+
`,
|
|
24
|
+
notes: `
|
|
25
|
+
stop ENDS the session; it does not background it. To keep an agent working
|
|
26
|
+
unattended instead, use \`agents sessions detach <id>\`, and bring it back
|
|
27
|
+
with \`agents sessions resume <id>\`.
|
|
28
|
+
|
|
29
|
+
A session that lives on another machine is stopped THERE over SSH — its pid
|
|
30
|
+
and tmux socket only mean something where it actually runs.
|
|
31
|
+
`,
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
export async function stopSessionAction(id, opts = {}) {
|
|
35
|
+
const { self, activeById } = await gatherLiveTargets(!!opts.local, { includeCloud: true });
|
|
36
|
+
const resolved = resolveOne(activeById, id);
|
|
37
|
+
if ('error' in resolved) {
|
|
38
|
+
console.error(chalk.red(resolved.error));
|
|
39
|
+
process.exitCode = 1;
|
|
40
|
+
return;
|
|
41
|
+
}
|
|
42
|
+
const s = resolved;
|
|
43
|
+
const target = resolveDetachTarget(s, self);
|
|
44
|
+
// Cloud/team/id-less sessions have their own lifecycles — refuse rather than
|
|
45
|
+
// half-stopping one from here (same boundary `detach` enforces).
|
|
46
|
+
if (target.kind === 'refuse') {
|
|
47
|
+
console.error(chalk.red(target.reason));
|
|
48
|
+
process.exitCode = 1;
|
|
49
|
+
return;
|
|
50
|
+
}
|
|
51
|
+
const short = target.sessionId.slice(0, 8);
|
|
52
|
+
// A session on another host: its pid/tmux socket only mean something where it
|
|
53
|
+
// runs, so stop it THERE over SSH — never kill locally. Mirrors detach/focus.
|
|
54
|
+
if (target.kind === 'remote') {
|
|
55
|
+
console.log(chalk.gray(`${short} lives on ${target.machine} — stopping it there over SSH…`));
|
|
56
|
+
const rc = await runOnPeer(['sessions', 'stop', target.sessionId, '--local'], target.machine);
|
|
57
|
+
if (rc === 'no-target') {
|
|
58
|
+
console.error(chalk.red(`Can't reach ${target.machine} to stop ${short}.`));
|
|
59
|
+
process.exitCode = 1;
|
|
60
|
+
}
|
|
61
|
+
return;
|
|
62
|
+
}
|
|
63
|
+
// Local: end the interactive process and tear down its tmux/mux session.
|
|
64
|
+
await stopInteractive(s);
|
|
65
|
+
console.log(chalk.green(`■ Stopped ${s.kind} ${short}`) + chalk.gray(' — process ended, session closed.'));
|
|
66
|
+
}
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import type { Command } from 'commander';
|
|
2
|
+
import type { SessionTrajectory } from '../lib/session/trajectory.js';
|
|
3
|
+
import type { TrajectoryComparison, TrajectoryDivergence, TrajectorySummary } from '../lib/session/trajectory-compare.js';
|
|
4
|
+
import type { TrajectoryStep } from '../lib/session/trajectory.js';
|
|
5
|
+
import type { LineageEdge, LineageNode, SessionLineage } from '../lib/session/trajectory-lineage.js';
|
|
6
|
+
/** Versioned envelope emitted by `--json` — the stable contract for consumers. */
|
|
7
|
+
export declare const SESSIONS_TRACE_SCHEMA_VERSION = 1;
|
|
8
|
+
/** The `diff` block of a `layout: 'compare'` envelope — everything but the two full trajectories, which already ride `sessions`. */
|
|
9
|
+
export interface SessionsTraceDiffEnvelope {
|
|
10
|
+
divergence?: TrajectoryDivergence;
|
|
11
|
+
added: TrajectoryStep[];
|
|
12
|
+
removed: TrajectoryStep[];
|
|
13
|
+
summaryA: TrajectorySummary;
|
|
14
|
+
summaryB: TrajectorySummary;
|
|
15
|
+
truncatedA: number;
|
|
16
|
+
truncatedB: number;
|
|
17
|
+
}
|
|
18
|
+
/** The `lineage` block of a `layout: 'lineage'` envelope — the delegation graph. */
|
|
19
|
+
export interface SessionsTraceLineageEnvelope {
|
|
20
|
+
rootId: string;
|
|
21
|
+
nodes: LineageNode[];
|
|
22
|
+
edges: LineageEdge[];
|
|
23
|
+
teams: string[];
|
|
24
|
+
unresolvedParentIds: string[];
|
|
25
|
+
}
|
|
26
|
+
/** The `--json` envelope: the versioned contract for the ext / triaging agents. */
|
|
27
|
+
export interface SessionsTraceEnvelope {
|
|
28
|
+
schemaVersion: typeof SESSIONS_TRACE_SCHEMA_VERSION;
|
|
29
|
+
kind: 'sessions-trace';
|
|
30
|
+
layout: 'single' | 'compare' | 'lineage';
|
|
31
|
+
sessions: SessionTrajectory[];
|
|
32
|
+
diff?: SessionsTraceDiffEnvelope;
|
|
33
|
+
lineage?: SessionsTraceLineageEnvelope;
|
|
34
|
+
}
|
|
35
|
+
/** Build the versioned `--json` envelope for a single-session trajectory. */
|
|
36
|
+
export declare function buildTraceEnvelope(models: SessionTrajectory[]): SessionsTraceEnvelope;
|
|
37
|
+
/** Build the versioned `--json` envelope for a two-session compare. */
|
|
38
|
+
export declare function buildCompareTraceEnvelope(cmp: TrajectoryComparison): SessionsTraceEnvelope;
|
|
39
|
+
/**
|
|
40
|
+
* Build the versioned `--json` envelope for a lineage. `sessions` carries the
|
|
41
|
+
* ROOT's trajectory only: the graph's own numbers come from the indexed session
|
|
42
|
+
* rows, so a consumer pays one transcript parse instead of one per teammate —
|
|
43
|
+
* `agents sessions trace <child>` is how you get a child's full trajectory.
|
|
44
|
+
*/
|
|
45
|
+
export declare function buildLineageTraceEnvelope(lineage: SessionLineage, rootTrajectory: SessionTrajectory): SessionsTraceEnvelope;
|
|
46
|
+
interface TraceOptions {
|
|
47
|
+
html?: boolean;
|
|
48
|
+
text?: boolean;
|
|
49
|
+
json?: boolean;
|
|
50
|
+
output?: string;
|
|
51
|
+
open?: boolean;
|
|
52
|
+
errorsOnly?: boolean;
|
|
53
|
+
redact?: boolean;
|
|
54
|
+
all?: boolean;
|
|
55
|
+
since?: string;
|
|
56
|
+
limit?: string;
|
|
57
|
+
agent?: string;
|
|
58
|
+
compare?: boolean;
|
|
59
|
+
tree?: boolean;
|
|
60
|
+
}
|
|
61
|
+
export type RenderFormat = 'html' | 'text' | 'json';
|
|
62
|
+
/**
|
|
63
|
+
* Pick the rendering by audience: explicit `--html/--text/--json` win; otherwise
|
|
64
|
+
* a person at a TTY gets the visual HTML and a piped/headless caller (an agent)
|
|
65
|
+
* gets the compact text trajectory.
|
|
66
|
+
*/
|
|
67
|
+
export declare function chooseFormat(options: TraceOptions, isTTY: boolean): RenderFormat;
|
|
68
|
+
/**
|
|
69
|
+
* Decide the trace layout from what the user TYPED (`selectorCount`) vs what
|
|
70
|
+
* those selectors RESOLVED to (`resolvedCount`). Keyed on selector count so a
|
|
71
|
+
* single content-search selector that happens to match two sessions never
|
|
72
|
+
* silently becomes a compare — every unsupported combination fails loud with a
|
|
73
|
+
* clear message. Pure, so the boundaries are unit-tested without the command.
|
|
74
|
+
*/
|
|
75
|
+
export declare function decideTraceLayout(options: Pick<TraceOptions, 'tree' | 'compare'>, selectorCount: number, resolvedCount: number): 'single' | 'compare' | 'lineage';
|
|
76
|
+
/** Attach the trace behaviour to a command node (canonical or top-level alias). */
|
|
77
|
+
export declare function configureTraceCommand(cmd: Command): Command;
|
|
78
|
+
/** Canonical `agents sessions trace <selectors...>`. */
|
|
79
|
+
export declare function registerSessionsTraceCommand(sessionsCmd: Command): void;
|
|
80
|
+
/** Top-level alias `agents trace <selectors...>` (mirrors `agents insights`). */
|
|
81
|
+
export declare function registerTraceCommand(program: Command): void;
|
|
82
|
+
export {};
|