@phnx-labs/agents-cli 1.22.45 → 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 +303 -0
- package/LICENSE +102 -182
- package/README.md +54 -17
- package/dist/cli/command-registry.d.ts +2 -1
- package/dist/cli/command-registry.js +6 -2
- package/dist/commands/accounts.js +56 -12
- package/dist/commands/auth.d.ts +2 -0
- package/dist/commands/auth.js +229 -0
- 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 +19 -32
- 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 +92 -6
- package/dist/lib/accounting/usage.js +329 -75
- 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 +20 -7
- 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/cli-resources.js +17 -15
- 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/harness-inventory.js +20 -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 +25 -3
- package/dist/lib/exec.js +75 -22
- 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/identity/client.d.ts +53 -0
- package/dist/lib/identity/client.js +106 -0
- package/dist/lib/identity/index.d.ts +115 -0
- package/dist/lib/identity/index.js +82 -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/probe.d.ts +8 -0
- package/dist/lib/probe.js +105 -0
- 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 +5 -2
- package/dist/lib/startup/command-registry.js +8 -5
- 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/view-types.d.ts +2 -2
- 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
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
import { type SessionStats } from './render.js';
|
|
2
|
+
import type { SessionEvent, SessionMeta } from './types.js';
|
|
3
|
+
/** One drawable step on a session's trajectory — a tool call or a thinking block. */
|
|
4
|
+
export interface TrajectoryStep {
|
|
5
|
+
/** 1-based position among drawn steps, stable for `--errors-only` and step refs. */
|
|
6
|
+
ordinal: number;
|
|
7
|
+
kind: 'tool' | 'thinking';
|
|
8
|
+
/** Tool name for `kind === 'tool'` (e.g. `Bash`, `Read`, `Task`). */
|
|
9
|
+
tool?: string;
|
|
10
|
+
/** The waterfall row this draws on — the tool name, or `think` for reasoning. */
|
|
11
|
+
lane: string;
|
|
12
|
+
/** Milliseconds from session start (the earliest event) to this step's start. */
|
|
13
|
+
startMs: number;
|
|
14
|
+
/**
|
|
15
|
+
* Duration in ms. Measured by pairing this `tool_use` with its `tool_result`
|
|
16
|
+
* on `callId`; when no paired result exists (or the harness omits `callId`),
|
|
17
|
+
* falls back to the delta to the next event and sets `durationEstimated`.
|
|
18
|
+
*/
|
|
19
|
+
durationMs: number;
|
|
20
|
+
/** True when `durationMs` is the next-event fallback, not a measured pairing. */
|
|
21
|
+
durationEstimated: boolean;
|
|
22
|
+
/** Structured harness outcome from the paired result; never inferred from text. */
|
|
23
|
+
outcome?: 'ok' | 'error' | 'unknown';
|
|
24
|
+
/** Redacted one-line summary (command / path / subagent / pattern). */
|
|
25
|
+
label: string;
|
|
26
|
+
/** Redacted, bounded output/evidence snippet from the paired result. */
|
|
27
|
+
detail?: string;
|
|
28
|
+
/** An inline `Task`/`Agent` sub-agent branch (a cross-session teammate is lineage). */
|
|
29
|
+
delegation?: 'inline-task' | 'teammate';
|
|
30
|
+
/** Output tokens attributed from the nearest following usage event. */
|
|
31
|
+
outputTokens?: number;
|
|
32
|
+
/** Harness-native call identity, when present. */
|
|
33
|
+
callId?: string;
|
|
34
|
+
/**
|
|
35
|
+
* For a shell tool call (`Bash`), the effective program the command actually
|
|
36
|
+
* ran — `git`, `gh`, `agents`, `bun`, `sed`… — extracted by the shared shell
|
|
37
|
+
* parser (`extractShellPrograms`, wrappers like `timeout`/`sudo`/`agents ssh`
|
|
38
|
+
* unwrapped). Lets a Bash-dominated trajectory read by what it DID, not a wall
|
|
39
|
+
* of "Bash". Undefined for non-shell tools or an unparseable command.
|
|
40
|
+
*/
|
|
41
|
+
program?: string;
|
|
42
|
+
/** Process exit code from the paired result (or the tool_use event), when the harness recorded one. */
|
|
43
|
+
exitCode?: number;
|
|
44
|
+
}
|
|
45
|
+
/** An idle stall between two events — a candidate "where did it hang" marker. */
|
|
46
|
+
export interface TrajectoryGap {
|
|
47
|
+
/** Milliseconds from session start to where the gap begins. */
|
|
48
|
+
startMs: number;
|
|
49
|
+
/** How long nothing happened, in ms. */
|
|
50
|
+
durationMs: number;
|
|
51
|
+
/** Ordinal of the last step before the gap (0 when the gap precedes step 1). */
|
|
52
|
+
afterOrdinal: number;
|
|
53
|
+
}
|
|
54
|
+
/** The derived trajectory of one session. */
|
|
55
|
+
export interface SessionTrajectory {
|
|
56
|
+
session: SessionMeta;
|
|
57
|
+
/** Wall-clock span of the session in ms (last event − first event). */
|
|
58
|
+
spanMs: number;
|
|
59
|
+
steps: TrajectoryStep[];
|
|
60
|
+
gaps: TrajectoryGap[];
|
|
61
|
+
/**
|
|
62
|
+
* Fraction of measured tool time per effective program, summing to ~1 (empty if
|
|
63
|
+
* no time). Keyed by the same tag the step list and badges use — a shell call's
|
|
64
|
+
* effective program (`git`, `bun`, `gh`), else the raw tool — so a Bash-heavy
|
|
65
|
+
* session reads as `git / bun / gh`, never a single opaque `Bash`. The single
|
|
66
|
+
* source both the HTML and text "where the time went" panels read.
|
|
67
|
+
*/
|
|
68
|
+
programTimeShare: Record<string, number>;
|
|
69
|
+
/**
|
|
70
|
+
* Failed steps — steps whose paired result carried `outcome: 'error'`. Distinct
|
|
71
|
+
* from `stats.errorCount`, which counts only standalone `type: 'error'` events;
|
|
72
|
+
* a tool that failed via a `tool_result` outcome is invisible there but counted
|
|
73
|
+
* here, so this is the truthful "how many calls failed" for the trajectory view.
|
|
74
|
+
*/
|
|
75
|
+
errorCount: number;
|
|
76
|
+
/** True when derived labels/details were secret-redacted (false under `--no-redact`). */
|
|
77
|
+
redacted: boolean;
|
|
78
|
+
/** Reused wholesale from `computeSummaryStats` — turns, tools, tokens, span. */
|
|
79
|
+
stats: SessionStats;
|
|
80
|
+
/** Steps dropped by the `maxSteps` cap, surfaced so truncation is never silent. */
|
|
81
|
+
truncatedSteps: number;
|
|
82
|
+
}
|
|
83
|
+
export interface BuildTrajectoryOptions {
|
|
84
|
+
/** Redact derived labels/details (default true; `--no-redact` is local-only). */
|
|
85
|
+
redact?: boolean;
|
|
86
|
+
/** Known secret values to mask, from `knownSecretValuesFromEnv()`. */
|
|
87
|
+
knownSecrets?: readonly string[];
|
|
88
|
+
/** A gap longer than this (ms) is recorded as an idle stall. Default 120000. */
|
|
89
|
+
idleThresholdMs?: number;
|
|
90
|
+
/** Cap on drawn steps; the tail is dropped and counted. Default 5000. */
|
|
91
|
+
maxSteps?: number;
|
|
92
|
+
}
|
|
93
|
+
/**
|
|
94
|
+
* Build the derived trajectory for one session's normalized events.
|
|
95
|
+
*
|
|
96
|
+
* Pairs each `tool_use` with its `tool_result`/`error` on `callId` (FIFO within a
|
|
97
|
+
* reused id — never across ids, and never by arrival order for concurrent calls,
|
|
98
|
+
* matching `ToolCallCollector.takePending`'s refusal to guess, `tool-calls.ts:509`).
|
|
99
|
+
* A harness with no parseable transcript (OpenClaw, `parse.ts:186`) yields an empty
|
|
100
|
+
* event array and therefore an empty trajectory — never a crash or a fabricated one.
|
|
101
|
+
*/
|
|
102
|
+
export declare function buildTrajectory(events: SessionEvent[], meta: SessionMeta, options?: BuildTrajectoryOptions): SessionTrajectory;
|
|
@@ -0,0 +1,335 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Derived session trajectory model — the one genuinely new computation behind
|
|
3
|
+
* `agents sessions trace`.
|
|
4
|
+
*
|
|
5
|
+
* Nothing in the session pipeline records a per-step DURATION: the indexed tool
|
|
6
|
+
* call (`tool-calls.ts`) keeps only a start timestamp, and `SessionEvent`
|
|
7
|
+
* (`types.ts:46`) carries a `tool_use` and its `tool_result` as two separate
|
|
8
|
+
* events. This module pairs them by `callId` to recover how long each call took,
|
|
9
|
+
* flags the idle gaps between events (stalls), and rolls the per-tool time up
|
|
10
|
+
* into a "where the time went" share — all DERIVED from the normalized event
|
|
11
|
+
* stream, never persisted onto `SessionEvent` or the SQLite `tool_calls` index.
|
|
12
|
+
*
|
|
13
|
+
* Pure and framework-free: it takes the parsed `SessionEvent[]` and the session
|
|
14
|
+
* metadata and returns a plain object the three renderers (HTML / text / JSON)
|
|
15
|
+
* project. Redaction reuses the same `redactSecrets` path as `render`/`share`
|
|
16
|
+
* (`redact.ts`), applied to the derived one-line labels; the label is the only
|
|
17
|
+
* place raw command/argument text reaches the model.
|
|
18
|
+
*
|
|
19
|
+
* The types (`SessionTrajectory`, `TrajectoryStep`) are shaped to also carry the
|
|
20
|
+
* later compare + lineage layouts (a stats block, gaps, and time-share per
|
|
21
|
+
* session), even though this PR renders only the single-session layout.
|
|
22
|
+
*/
|
|
23
|
+
import { redactSecrets } from '../redact.js';
|
|
24
|
+
import { computeSummaryStats } from './render.js';
|
|
25
|
+
import { extractShellPrograms } from './shell-programs.js';
|
|
26
|
+
const DEFAULT_IDLE_THRESHOLD_MS = 120_000;
|
|
27
|
+
const DEFAULT_MAX_STEPS = 5000;
|
|
28
|
+
const LABEL_MAX = 140;
|
|
29
|
+
const DETAIL_MAX = 400;
|
|
30
|
+
/** Tools that spawn an inline sub-agent inside THIS transcript (a `tool_use` row). */
|
|
31
|
+
const INLINE_TASK_TOOLS = new Set(['Task', 'Agent']);
|
|
32
|
+
/**
|
|
33
|
+
* Shell tools whose command is worth resolving to an effective program — every
|
|
34
|
+
* harness's shell-exec tool, kept in lockstep with the canonical set in
|
|
35
|
+
* `state.ts` (`['Bash', 'exec_command', 'run_shell_command', 'shell', 'Execute']`)
|
|
36
|
+
* so Codex's `exec_command` and the rest are covered, not just Claude's `Bash`.
|
|
37
|
+
*/
|
|
38
|
+
const SHELL_TOOLS = new Set(['Bash', 'exec_command', 'run_shell_command', 'shell', 'Execute']);
|
|
39
|
+
/**
|
|
40
|
+
* Shell builtins/assignments that are rarely the POINT of a command — the action
|
|
41
|
+
* is whatever runs after them (`export X=Y; git push` → `git`, `cd dir && bun test`
|
|
42
|
+
* → `bun`). Skipped when a real program follows so a Bash row reads by what it did.
|
|
43
|
+
*/
|
|
44
|
+
const SHELL_NOISE_PROGRAMS = new Set(['export', 'cd', 'set', 'source', '.', 'unset', 'local', 'eval']);
|
|
45
|
+
/**
|
|
46
|
+
* The effective program a shell command ran, via the shared shell parser
|
|
47
|
+
* (`extractShellPrograms` — which unwraps the wrappers it knows: `env`, `sudo`,
|
|
48
|
+
* `agents ssh`; note `timeout` is deliberately NOT a wrapper there, per SES-37,
|
|
49
|
+
* so `timeout 30 npm test` resolves to `timeout`). On top of that this skips
|
|
50
|
+
* bare builtins/assignments (`cd`, `export`, …) when a real program follows. For
|
|
51
|
+
* a pipeline it takes the LEFTMOST effective program (`cat f | grep x` → `cat`),
|
|
52
|
+
* matching how the parser orders occurrences. Undefined when nothing static is
|
|
53
|
+
* identifiable. Never executes anything.
|
|
54
|
+
*/
|
|
55
|
+
function effectiveProgram(command) {
|
|
56
|
+
if (!command)
|
|
57
|
+
return undefined;
|
|
58
|
+
const { occurrences, programs } = extractShellPrograms(command);
|
|
59
|
+
const effective = occurrences.filter((o) => o.role === 'effective').map((o) => o.program);
|
|
60
|
+
const meaningful = effective.find((p) => !SHELL_NOISE_PROGRAMS.has(p))
|
|
61
|
+
?? programs.find((p) => !SHELL_NOISE_PROGRAMS.has(p));
|
|
62
|
+
return meaningful ?? effective[0] ?? programs[0];
|
|
63
|
+
}
|
|
64
|
+
function toMs(timestamp) {
|
|
65
|
+
const ms = new Date(timestamp).getTime();
|
|
66
|
+
return Number.isNaN(ms) ? NaN : ms;
|
|
67
|
+
}
|
|
68
|
+
function firstLine(text) {
|
|
69
|
+
const nl = text.indexOf('\n');
|
|
70
|
+
return nl === -1 ? text : text.slice(0, nl);
|
|
71
|
+
}
|
|
72
|
+
function clip(text, max) {
|
|
73
|
+
const collapsed = firstLine(text).replace(/\s+/g, ' ').trim();
|
|
74
|
+
return collapsed.length <= max ? collapsed : `${collapsed.slice(0, max - 1)}…`;
|
|
75
|
+
}
|
|
76
|
+
function stringArg(args, ...keys) {
|
|
77
|
+
if (!args)
|
|
78
|
+
return undefined;
|
|
79
|
+
for (const key of keys) {
|
|
80
|
+
const value = args[key];
|
|
81
|
+
if (typeof value === 'string' && value.length > 0)
|
|
82
|
+
return value;
|
|
83
|
+
}
|
|
84
|
+
return undefined;
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* A redacted one-line label for a tool call, derived from the raw arguments.
|
|
88
|
+
*
|
|
89
|
+
* This is the single place unredacted command/argument text enters the model, so
|
|
90
|
+
* it runs through the same `redactSecrets` pass `render`/`share` use. `--no-redact`
|
|
91
|
+
* (local only) skips that pass; the caller passes `redact: false`.
|
|
92
|
+
*/
|
|
93
|
+
function toolLabel(tool, args, command, redact, knownSecrets) {
|
|
94
|
+
let raw;
|
|
95
|
+
const lower = tool.toLowerCase();
|
|
96
|
+
if (lower === 'bash' || lower === 'shell' || lower === 'run_command' || lower.includes('exec')) {
|
|
97
|
+
raw = command ?? stringArg(args, 'command', 'cmd', 'script');
|
|
98
|
+
}
|
|
99
|
+
else if (lower === 'read' || lower === 'edit' || lower === 'write' || lower === 'notebookedit' || lower === 'multiedit') {
|
|
100
|
+
raw = stringArg(args, 'file_path', 'path', 'notebook_path', 'filePath');
|
|
101
|
+
}
|
|
102
|
+
else if (lower === 'grep' || lower === 'glob' || lower === 'search' || lower === 'codebase_search') {
|
|
103
|
+
raw = stringArg(args, 'pattern', 'query', 'q');
|
|
104
|
+
}
|
|
105
|
+
else if (lower === 'task' || lower === 'agent') {
|
|
106
|
+
const subagent = stringArg(args, 'subagent_type', 'agentType', 'subagent');
|
|
107
|
+
const description = stringArg(args, 'description', 'prompt');
|
|
108
|
+
raw = subagent ? (description ? `${subagent}: ${description}` : subagent) : description;
|
|
109
|
+
}
|
|
110
|
+
else if (lower === 'webfetch' || lower === 'websearch' || lower === 'web_search') {
|
|
111
|
+
raw = stringArg(args, 'url', 'query', 'q');
|
|
112
|
+
}
|
|
113
|
+
else {
|
|
114
|
+
raw = stringArg(args, 'command', 'cmd', 'file_path', 'path', 'query', 'pattern', 'url', 'description');
|
|
115
|
+
}
|
|
116
|
+
const base = raw && raw.trim().length > 0 ? clip(raw, LABEL_MAX) : tool;
|
|
117
|
+
return redact ? redactSecrets(base, knownSecrets) : base;
|
|
118
|
+
}
|
|
119
|
+
function resultOutcome(event) {
|
|
120
|
+
if (event.outcome === 'ok' || event.outcome === 'error' || event.outcome === 'unknown')
|
|
121
|
+
return event.outcome;
|
|
122
|
+
if (event.success === true)
|
|
123
|
+
return 'ok';
|
|
124
|
+
if (event.success === false)
|
|
125
|
+
return 'error';
|
|
126
|
+
if (event.type === 'error')
|
|
127
|
+
return 'error';
|
|
128
|
+
return 'unknown';
|
|
129
|
+
}
|
|
130
|
+
function resultDetail(event, redact, knownSecrets) {
|
|
131
|
+
const text = event.output ?? event.content;
|
|
132
|
+
if (!text || text.trim().length === 0)
|
|
133
|
+
return undefined;
|
|
134
|
+
const clipped = clip(text, DETAIL_MAX);
|
|
135
|
+
return redact ? redactSecrets(clipped, knownSecrets) : clipped;
|
|
136
|
+
}
|
|
137
|
+
/**
|
|
138
|
+
* Build the derived trajectory for one session's normalized events.
|
|
139
|
+
*
|
|
140
|
+
* Pairs each `tool_use` with its `tool_result`/`error` on `callId` (FIFO within a
|
|
141
|
+
* reused id — never across ids, and never by arrival order for concurrent calls,
|
|
142
|
+
* matching `ToolCallCollector.takePending`'s refusal to guess, `tool-calls.ts:509`).
|
|
143
|
+
* A harness with no parseable transcript (OpenClaw, `parse.ts:186`) yields an empty
|
|
144
|
+
* event array and therefore an empty trajectory — never a crash or a fabricated one.
|
|
145
|
+
*/
|
|
146
|
+
export function buildTrajectory(events, meta, options = {}) {
|
|
147
|
+
const redact = options.redact !== false;
|
|
148
|
+
const knownSecrets = options.knownSecrets;
|
|
149
|
+
const idleThreshold = options.idleThresholdMs ?? DEFAULT_IDLE_THRESHOLD_MS;
|
|
150
|
+
const maxSteps = options.maxSteps ?? DEFAULT_MAX_STEPS;
|
|
151
|
+
const stats = computeSummaryStats(events);
|
|
152
|
+
const firstTs = stats.firstTs;
|
|
153
|
+
const spanMs = stats.lastTs > stats.firstTs ? stats.lastTs - stats.firstTs : 0;
|
|
154
|
+
// Absolute ms per event index (NaN when the timestamp is unparseable).
|
|
155
|
+
const eventMs = events.map((e) => toMs(e.timestamp));
|
|
156
|
+
// The next event index (after i) that carries a valid timestamp — the anchor
|
|
157
|
+
// for the next-event duration fallback and for idle-gap detection.
|
|
158
|
+
const nextValidTs = new Array(events.length).fill(NaN);
|
|
159
|
+
for (let i = events.length - 1, later = NaN; i >= 0; i--) {
|
|
160
|
+
nextValidTs[i] = later;
|
|
161
|
+
if (!Number.isNaN(eventMs[i]))
|
|
162
|
+
later = eventMs[i];
|
|
163
|
+
}
|
|
164
|
+
// Draw a step for each thinking block and each non-local tool_use, in order.
|
|
165
|
+
// `_local` tool calls (Claude `!`-prefixed shell) are excluded to match the
|
|
166
|
+
// header's tool count (`computeSummaryStats` skips them, `render.ts:224`).
|
|
167
|
+
const drafts = [];
|
|
168
|
+
const pendingByCallId = new Map();
|
|
169
|
+
for (let i = 0; i < events.length; i++) {
|
|
170
|
+
const e = events[i];
|
|
171
|
+
if (e.type === 'thinking') {
|
|
172
|
+
drafts.push({
|
|
173
|
+
step: {
|
|
174
|
+
ordinal: 0,
|
|
175
|
+
kind: 'thinking',
|
|
176
|
+
lane: 'think',
|
|
177
|
+
startMs: Number.isNaN(eventMs[i]) ? 0 : Math.max(0, eventMs[i] - firstTs),
|
|
178
|
+
durationMs: 0,
|
|
179
|
+
durationEstimated: true,
|
|
180
|
+
label: 'thinking',
|
|
181
|
+
},
|
|
182
|
+
eventIndex: i,
|
|
183
|
+
});
|
|
184
|
+
}
|
|
185
|
+
else if (e.type === 'tool_use' && !e._local) {
|
|
186
|
+
const tool = e.tool || 'unknown';
|
|
187
|
+
const draftIndex = drafts.length;
|
|
188
|
+
drafts.push({
|
|
189
|
+
step: {
|
|
190
|
+
ordinal: 0,
|
|
191
|
+
kind: 'tool',
|
|
192
|
+
tool,
|
|
193
|
+
lane: tool,
|
|
194
|
+
startMs: Number.isNaN(eventMs[i]) ? 0 : Math.max(0, eventMs[i] - firstTs),
|
|
195
|
+
durationMs: 0,
|
|
196
|
+
durationEstimated: true,
|
|
197
|
+
outcome: 'unknown',
|
|
198
|
+
label: toolLabel(tool, e.args, e.command, redact, knownSecrets),
|
|
199
|
+
delegation: INLINE_TASK_TOOLS.has(tool) ? 'inline-task' : undefined,
|
|
200
|
+
callId: e.callId,
|
|
201
|
+
program: SHELL_TOOLS.has(tool) ? effectiveProgram(e.command ?? (typeof e.args?.command === 'string' ? e.args.command : undefined)) : undefined,
|
|
202
|
+
exitCode: typeof e.exitCode === 'number' ? e.exitCode : undefined,
|
|
203
|
+
},
|
|
204
|
+
eventIndex: i,
|
|
205
|
+
callId: e.callId,
|
|
206
|
+
});
|
|
207
|
+
if (e.callId) {
|
|
208
|
+
const queue = pendingByCallId.get(e.callId);
|
|
209
|
+
if (queue)
|
|
210
|
+
queue.push(draftIndex);
|
|
211
|
+
else
|
|
212
|
+
pendingByCallId.set(e.callId, [draftIndex]);
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
else if ((e.type === 'tool_result' || e.type === 'error') && e.callId) {
|
|
216
|
+
// Pair strictly by callId, FIFO within a reused id — never across ids.
|
|
217
|
+
const queue = pendingByCallId.get(e.callId);
|
|
218
|
+
if (queue && queue.length > 0) {
|
|
219
|
+
const draftIndex = queue.shift();
|
|
220
|
+
drafts[draftIndex].resultEventIndex = i;
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
// Resolve durations, outcomes, and detail now that pairing is complete.
|
|
225
|
+
for (const draft of drafts) {
|
|
226
|
+
const { step } = draft;
|
|
227
|
+
const startAbs = Number.isNaN(eventMs[draft.eventIndex]) ? NaN : eventMs[draft.eventIndex];
|
|
228
|
+
if (draft.resultEventIndex !== undefined) {
|
|
229
|
+
const resultEvent = events[draft.resultEventIndex];
|
|
230
|
+
const resultAbs = eventMs[draft.resultEventIndex];
|
|
231
|
+
if (!Number.isNaN(startAbs) && !Number.isNaN(resultAbs)) {
|
|
232
|
+
step.durationMs = Math.max(0, resultAbs - startAbs);
|
|
233
|
+
step.durationEstimated = false;
|
|
234
|
+
}
|
|
235
|
+
step.outcome = resultOutcome(resultEvent);
|
|
236
|
+
// Prefer the exit code recorded on the paired result; keep the tool_use
|
|
237
|
+
// event's code (set at draft time) only when the result carries none.
|
|
238
|
+
if (typeof resultEvent.exitCode === 'number')
|
|
239
|
+
step.exitCode = resultEvent.exitCode;
|
|
240
|
+
const detail = resultDetail(resultEvent, redact, knownSecrets);
|
|
241
|
+
if (detail)
|
|
242
|
+
step.detail = detail;
|
|
243
|
+
}
|
|
244
|
+
else {
|
|
245
|
+
// No paired result — fall back to the delta to the next timestamped event.
|
|
246
|
+
const nextAbs = nextValidTs[draft.eventIndex];
|
|
247
|
+
if (!Number.isNaN(startAbs) && !Number.isNaN(nextAbs)) {
|
|
248
|
+
step.durationMs = Math.max(0, nextAbs - startAbs);
|
|
249
|
+
}
|
|
250
|
+
step.durationEstimated = true;
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
// Attribute output tokens: each usage event lands on the most recent tool step
|
|
254
|
+
// (the nearest following usage from that step's point of view).
|
|
255
|
+
let lastToolDraft;
|
|
256
|
+
const draftByEventIndex = new Map();
|
|
257
|
+
for (const draft of drafts)
|
|
258
|
+
draftByEventIndex.set(draft.eventIndex, draft);
|
|
259
|
+
for (let i = 0; i < events.length; i++) {
|
|
260
|
+
const draft = draftByEventIndex.get(i);
|
|
261
|
+
if (draft && draft.step.kind === 'tool')
|
|
262
|
+
lastToolDraft = draft;
|
|
263
|
+
else if (events[i].type === 'usage' && lastToolDraft) {
|
|
264
|
+
const out = events[i].outputTokens ?? 0;
|
|
265
|
+
if (out > 0)
|
|
266
|
+
lastToolDraft.step.outputTokens = (lastToolDraft.step.outputTokens ?? 0) + out;
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
// Number the steps and apply the cap (tail dropped, counted — never silent).
|
|
270
|
+
let steps = drafts.map((d) => d.step);
|
|
271
|
+
let truncatedSteps = 0;
|
|
272
|
+
if (steps.length > maxSteps) {
|
|
273
|
+
truncatedSteps = steps.length - maxSteps;
|
|
274
|
+
steps = steps.slice(0, maxSteps);
|
|
275
|
+
}
|
|
276
|
+
steps.forEach((step, index) => { step.ordinal = index + 1; });
|
|
277
|
+
// Idle gaps: a long delta between consecutive timestamped events. `afterOrdinal`
|
|
278
|
+
// is the last step whose event index precedes the gap.
|
|
279
|
+
const gaps = [];
|
|
280
|
+
const orderedTs = [];
|
|
281
|
+
for (let i = 0; i < events.length; i++) {
|
|
282
|
+
if (!Number.isNaN(eventMs[i]))
|
|
283
|
+
orderedTs.push({ index: i, ms: eventMs[i] });
|
|
284
|
+
}
|
|
285
|
+
const stepOrdinalAtOrBefore = (eventIndex) => {
|
|
286
|
+
let ordinal = 0;
|
|
287
|
+
for (const d of drafts) {
|
|
288
|
+
if (d.eventIndex <= eventIndex && d.step.ordinal > 0)
|
|
289
|
+
ordinal = d.step.ordinal;
|
|
290
|
+
else if (d.eventIndex > eventIndex)
|
|
291
|
+
break;
|
|
292
|
+
}
|
|
293
|
+
return ordinal;
|
|
294
|
+
};
|
|
295
|
+
for (let k = 1; k < orderedTs.length; k++) {
|
|
296
|
+
// A span that OPENS on a `tool_use` is that tool executing (waiting on its
|
|
297
|
+
// own result), not the agent sitting idle — so it is not a stall. Counting
|
|
298
|
+
// it double-reports a long `bun test` as both its step duration and a fake
|
|
299
|
+
// "idle" gap. Only spans that open after a completed boundary are idle.
|
|
300
|
+
if (events[orderedTs[k - 1].index].type === 'tool_use')
|
|
301
|
+
continue;
|
|
302
|
+
const delta = orderedTs[k].ms - orderedTs[k - 1].ms;
|
|
303
|
+
if (delta > idleThreshold) {
|
|
304
|
+
gaps.push({
|
|
305
|
+
startMs: Math.max(0, orderedTs[k - 1].ms - firstTs),
|
|
306
|
+
durationMs: delta,
|
|
307
|
+
afterOrdinal: stepOrdinalAtOrBefore(orderedTs[k - 1].index),
|
|
308
|
+
});
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
// Where the time went — measured tool duration share per tool.
|
|
312
|
+
// Sum tool time by the EFFECTIVE PROGRAM (a shell call's `git`/`bun`/`gh`, else
|
|
313
|
+
// the raw tool) — the same tag the step list and badges show — so both renderers
|
|
314
|
+
// read one program-keyed source instead of each re-bucketing the steps.
|
|
315
|
+
const perProgram = {};
|
|
316
|
+
let totalToolMs = 0;
|
|
317
|
+
for (const step of steps) {
|
|
318
|
+
if (step.kind !== 'tool')
|
|
319
|
+
continue;
|
|
320
|
+
const tag = step.program ?? step.tool;
|
|
321
|
+
if (!tag)
|
|
322
|
+
continue;
|
|
323
|
+
perProgram[tag] = (perProgram[tag] ?? 0) + step.durationMs;
|
|
324
|
+
totalToolMs += step.durationMs;
|
|
325
|
+
}
|
|
326
|
+
const programTimeShare = {};
|
|
327
|
+
if (totalToolMs > 0) {
|
|
328
|
+
for (const [tag, ms] of Object.entries(perProgram)) {
|
|
329
|
+
if (ms > 0)
|
|
330
|
+
programTimeShare[tag] = ms / totalToolMs;
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
const errorCount = steps.reduce((n, s) => (s.outcome === 'error' ? n + 1 : n), 0);
|
|
334
|
+
return { session: meta, spanMs, steps, gaps, programTimeShare, errorCount, redacted: redact, stats, truncatedSteps };
|
|
335
|
+
}
|
|
@@ -30,8 +30,11 @@ export declare const KNOWN_TOP_LEVEL_COMMANDS: ReadonlySet<string>;
|
|
|
30
30
|
* read-only local web companion + `--control` anchor) was removed with the
|
|
31
31
|
* unshipped iOS Fleet Cockpit it existed for (RUSH-3001). `apply` nested under
|
|
32
32
|
* `agents fleet apply` / `agents devices apply`. `beta` nested under
|
|
33
|
-
* `agents setup beta` (RUSH-2981). `
|
|
34
|
-
*
|
|
33
|
+
* `agents setup beta` (RUSH-2981). `org` (the Prix-coupled account layer) was
|
|
34
|
+
* removed; `agents auth` returned against Phoenix ID with `auth space` as the
|
|
35
|
+
* team surface (RUSH-2581). `usage` was removed as a duplicate surface of
|
|
36
|
+
* `agents view`, which renders per-account usage with account, version, and
|
|
37
|
+
* auth state beside it (RUSH-3079).
|
|
35
38
|
*/
|
|
36
39
|
export declare const RETIRED_TOP_LEVEL_COMMANDS: ReadonlySet<string>;
|
|
37
40
|
export declare function isKnownTopLevelCommand(name: string): boolean;
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
const LOADED_COMMAND_NAMES = [
|
|
2
|
-
'accounts', 'view', 'inspect', 'feedback', 'commands', 'hooks', 'skills', 'rules', 'memory',
|
|
2
|
+
'accounts', 'auth', 'view', 'inspect', 'feedback', 'commands', 'hooks', 'skills', 'rules', 'memory',
|
|
3
3
|
'permissions', 'mcp', 'clis', 'subagents', 'plugins', 'workflows', 'add', 'use', 'list',
|
|
4
4
|
'remove', 'rm', 'purge', 'update', 'prune', 'import', 'registry', 'search', 'install',
|
|
5
5
|
'routines', 'monitors', 'projects', 'run', 'open', 'reconnect', 'fork', 'config',
|
|
6
6
|
'models', 'modes', 'trash', 'restore', 'doctor',
|
|
7
7
|
'route', 'harness', 'harnesses', 'secrets', 'menubar', 'sync',
|
|
8
|
-
'refresh-rules', 'factory', '
|
|
8
|
+
'refresh-rules', 'factory', 'insights', 'trace', 'perf',
|
|
9
9
|
'pty', 'tmux', 'watchdog', 'browser', 'computer', 'logs', 'events',
|
|
10
10
|
'ssh', 'devices', 'fleet', 'repos', 'repo', 'setup', 'uninstall', 'upgrade', 'sessions',
|
|
11
11
|
'teams', 'cloud', 'message', 'send', 'notify', 'feed',
|
|
@@ -50,14 +50,17 @@ export const KNOWN_TOP_LEVEL_COMMANDS = new Set([
|
|
|
50
50
|
* read-only local web companion + `--control` anchor) was removed with the
|
|
51
51
|
* unshipped iOS Fleet Cockpit it existed for (RUSH-3001). `apply` nested under
|
|
52
52
|
* `agents fleet apply` / `agents devices apply`. `beta` nested under
|
|
53
|
-
* `agents setup beta` (RUSH-2981). `
|
|
54
|
-
*
|
|
53
|
+
* `agents setup beta` (RUSH-2981). `org` (the Prix-coupled account layer) was
|
|
54
|
+
* removed; `agents auth` returned against Phoenix ID with `auth space` as the
|
|
55
|
+
* team surface (RUSH-2581). `usage` was removed as a duplicate surface of
|
|
56
|
+
* `agents view`, which renders per-account usage with account, version, and
|
|
57
|
+
* auth state beside it (RUSH-3079).
|
|
55
58
|
*/
|
|
56
59
|
export const RETIRED_TOP_LEVEL_COMMANDS = new Set([
|
|
57
60
|
'webhook',
|
|
58
|
-
'auth',
|
|
59
61
|
'org',
|
|
60
62
|
'serve',
|
|
63
|
+
'usage',
|
|
61
64
|
'login',
|
|
62
65
|
'logout',
|
|
63
66
|
'budget',
|
package/dist/lib/state.d.ts
CHANGED
|
@@ -258,7 +258,7 @@ export declare function getTeamsAgentsDir(): string;
|
|
|
258
258
|
export declare function getTeamsRegistryPath(): string;
|
|
259
259
|
/** Path to the device registry — SSH device profiles with platform/auth metadata. Durable runtime, per-machine (host list + addresses are NOT pulled by `agents repo push`). */
|
|
260
260
|
export declare function getDevicesRegistryPath(): string;
|
|
261
|
-
/** Path to the device ignore-list —
|
|
261
|
+
/** Path to the LEGACY per-machine device ignore-list — superseded by the tracked `fleet.ignored` list in central agents.yaml (RUSH-3062); only lib/devices/config-migration.ts still reads it (to fold + remove it). */
|
|
262
262
|
export declare function getDevicesIgnoredPath(): string;
|
|
263
263
|
/** Path to the LEGACY device auto-launch preference file — which registered devices are eligible/preferred for the ext's auto-host selection. Superseded by the per-device doc `config:` block; only lib/devices/config-migration.ts still reads it (to fold + remove it). */
|
|
264
264
|
export declare function getDevicesAutoLaunchPath(): string;
|
package/dist/lib/state.js
CHANGED
|
@@ -563,7 +563,9 @@ export function getTeamsAgentsDir() { return TEAMS_AGENTS_DIR; }
|
|
|
563
563
|
/** Path to the team registry — list of named teams with timestamps. Durable runtime, per-machine. */
|
|
564
564
|
export function getTeamsRegistryPath() { return path.join(HISTORY_DIR, 'teams', 'registry.json'); }
|
|
565
565
|
/**
|
|
566
|
-
* The devices dir (registry
|
|
566
|
+
* The devices dir (the registry lives here; the ignore-list moved to the
|
|
567
|
+
* tracked central agents.yaml as `fleet.ignored` — RUSH-3062). Read at CALL
|
|
568
|
+
* time so a
|
|
567
569
|
* test can redirect it to a temp dir via AGENTS_DEVICES_DIR without racing the
|
|
568
570
|
* module-load capture of HISTORY_DIR — mirrors the AGENTS_EVENTS_PATH /
|
|
569
571
|
* AGENTS_SECRETS_AGENT_DIR test-isolation escape hatches. Never set in
|
|
@@ -575,7 +577,7 @@ function getDevicesDir() {
|
|
|
575
577
|
}
|
|
576
578
|
/** Path to the device registry — SSH device profiles with platform/auth metadata. Durable runtime, per-machine (host list + addresses are NOT pulled by `agents repo push`). */
|
|
577
579
|
export function getDevicesRegistryPath() { return path.join(getDevicesDir(), 'registry.json'); }
|
|
578
|
-
/** Path to the device ignore-list —
|
|
580
|
+
/** Path to the LEGACY per-machine device ignore-list — superseded by the tracked `fleet.ignored` list in central agents.yaml (RUSH-3062); only lib/devices/config-migration.ts still reads it (to fold + remove it). */
|
|
579
581
|
export function getDevicesIgnoredPath() { return path.join(getDevicesDir(), 'ignored.json'); }
|
|
580
582
|
/** Path to the LEGACY device auto-launch preference file — which registered devices are eligible/preferred for the ext's auto-host selection. Superseded by the per-device doc `config:` block; only lib/devices/config-migration.ts still reads it (to fold + remove it). */
|
|
581
583
|
export function getDevicesAutoLaunchPath() { return path.join(getDevicesDir(), 'auto-launch.json'); }
|
package/dist/lib/teams/agents.js
CHANGED
|
@@ -1451,6 +1451,15 @@ export class AgentManager {
|
|
|
1451
1451
|
}
|
|
1452
1452
|
this.defaultMode = resolvedDefaultMode;
|
|
1453
1453
|
this.initPromise = this.doInitialize();
|
|
1454
|
+
// Mark the deferred rejection as observed. Construction fires init
|
|
1455
|
+
// fire-and-forget; every public method still surfaces a failed init at its
|
|
1456
|
+
// own `await this.initialize()` (the same promise rejects for each new
|
|
1457
|
+
// awaiter). Without this, an init that loses a race with its directory
|
|
1458
|
+
// being removed — measured twice as an unhandled
|
|
1459
|
+
// `ENOENT mkdir /tmp/agents-retention-*` that failed a fully-green suite
|
|
1460
|
+
// (exit 1 with 12k tests passed) and blocked release attestation — crashes
|
|
1461
|
+
// the process instead of failing the caller that actually cares.
|
|
1462
|
+
this.initPromise.catch(() => { });
|
|
1454
1463
|
}
|
|
1455
1464
|
async initialize() {
|
|
1456
1465
|
if (!this.initPromise) {
|
|
@@ -98,12 +98,31 @@ export interface InjectOptions {
|
|
|
98
98
|
export interface InjectResult {
|
|
99
99
|
ok: boolean;
|
|
100
100
|
backend: InjectBackend;
|
|
101
|
+
/**
|
|
102
|
+
* Delivery is CONFIRMED to have reached the running agent, not merely dispatched.
|
|
103
|
+
*
|
|
104
|
+
* For tmux / iterm / ghostty / pty, `ok` already means delivered: `tmux
|
|
105
|
+
* send-keys` errors on a missing pane, `osascript write text` errors on a bad
|
|
106
|
+
* session id, and a pty write errors if the sidecar can't reach the fd — so a
|
|
107
|
+
* successful transport IS the confirmation. For **vscodium**, `ok` only means the
|
|
108
|
+
* editor CLI accepted the `--open-url` hand-off; whether the swarm-ext extension
|
|
109
|
+
* actually parsed the verb and typed anything is unknown from the CLI side (the
|
|
110
|
+
* ext must ack — RUSH follow-up). So vscodium is delivered-but-UNCONFIRMED, and
|
|
111
|
+
* the watchdog must not book a nudge as landed on that alone.
|
|
112
|
+
*/
|
|
113
|
+
confirmed: boolean;
|
|
101
114
|
/** Discrete writes delivered: 2 for the Ink-safe text+Enter split, 1 when combined or enter=false. */
|
|
102
115
|
writes: number;
|
|
103
116
|
/** For the tmux / AppleScript backends (or any dryRun), the spec(s) that ran / would run. */
|
|
104
117
|
specs?: LaunchSpec[];
|
|
105
118
|
error?: string;
|
|
106
119
|
}
|
|
120
|
+
/**
|
|
121
|
+
* Whether a successful transport on this backend CONFIRMS delivery to the agent.
|
|
122
|
+
* Everything but vscodium self-confirms (see InjectResult.confirmed); vscodium's
|
|
123
|
+
* `--open-url` is fire-and-forget until the swarm-ext extension acks the verb.
|
|
124
|
+
*/
|
|
125
|
+
export declare function backendConfirmsDelivery(backend: InjectBackend): boolean;
|
|
107
126
|
/**
|
|
108
127
|
* argv for `tmux send-keys` targeting a pane by id. Starts with `tmux` (like the
|
|
109
128
|
* engine's `tmuxTabArgv`) so the same transport runs it locally or over SSH. The
|
|
@@ -46,6 +46,14 @@ import { appleScriptStr } from './quote.js';
|
|
|
46
46
|
import { runSpec } from './transport.js';
|
|
47
47
|
import { itermBackend, ghosttyBackend } from './backends/index.js';
|
|
48
48
|
import { currentContext } from './types.js';
|
|
49
|
+
/**
|
|
50
|
+
* Whether a successful transport on this backend CONFIRMS delivery to the agent.
|
|
51
|
+
* Everything but vscodium self-confirms (see InjectResult.confirmed); vscodium's
|
|
52
|
+
* `--open-url` is fire-and-forget until the swarm-ext extension acks the verb.
|
|
53
|
+
*/
|
|
54
|
+
export function backendConfirmsDelivery(backend) {
|
|
55
|
+
return backend !== 'vscodium';
|
|
56
|
+
}
|
|
49
57
|
/** Carriage return — what Enter delivers into a raw PTY/tmux byte stream. */
|
|
50
58
|
const CR = '\r';
|
|
51
59
|
// --- tmux -------------------------------------------------------------------
|
|
@@ -180,22 +188,23 @@ export function vscodiumInjectSpec(target, text, opts) {
|
|
|
180
188
|
// --- pty --------------------------------------------------------------------
|
|
181
189
|
async function injectPty(target, text, o) {
|
|
182
190
|
if (o.host && o.host !== 'local') {
|
|
183
|
-
return { ok: false, backend: 'pty', writes: 0, error: 'pty injection is local-only (the sidecar is not a remote surface)' };
|
|
191
|
+
return { ok: false, confirmed: false, backend: 'pty', writes: 0, error: 'pty injection is local-only (the sidecar is not a remote surface)' };
|
|
184
192
|
}
|
|
185
193
|
// Same Ink-safe split as tmux: text write, then a separate CR write.
|
|
186
194
|
const writes = o.enter && o.combined ? [text + CR] : o.enter ? [text, CR] : [text];
|
|
187
195
|
if (o.dryRun)
|
|
188
|
-
return { ok: true, backend: 'pty', writes: writes.length };
|
|
196
|
+
return { ok: true, confirmed: true, backend: 'pty', writes: writes.length };
|
|
189
197
|
try {
|
|
190
198
|
for (const input of writes) {
|
|
191
199
|
const res = await ptyRequest('write', target.id, { input });
|
|
192
200
|
if (!res.ok)
|
|
193
|
-
return { ok: false, backend: 'pty', writes: 0, error: res.error ?? 'pty write failed' };
|
|
201
|
+
return { ok: false, confirmed: false, backend: 'pty', writes: 0, error: res.error ?? 'pty write failed' };
|
|
194
202
|
}
|
|
195
|
-
|
|
203
|
+
// A successful sidecar write reached the fd — real delivery.
|
|
204
|
+
return { ok: true, confirmed: true, backend: 'pty', writes: writes.length };
|
|
196
205
|
}
|
|
197
206
|
catch (err) {
|
|
198
|
-
return { ok: false, backend: 'pty', writes: 0, error: err instanceof Error ? err.message : String(err) };
|
|
207
|
+
return { ok: false, confirmed: false, backend: 'pty', writes: 0, error: err instanceof Error ? err.message : String(err) };
|
|
199
208
|
}
|
|
200
209
|
}
|
|
201
210
|
// --- public entry -----------------------------------------------------------
|
|
@@ -225,8 +234,9 @@ export async function injectIntoTerminal(target, text, opts = {}) {
|
|
|
225
234
|
: target.backend === 'ghostty'
|
|
226
235
|
? enter ? 2 : 1
|
|
227
236
|
: enter && !combined ? 2 : 1;
|
|
237
|
+
const confirmed = backendConfirmsDelivery(target.backend);
|
|
228
238
|
if (opts.dryRun)
|
|
229
|
-
return { ok: true, backend: target.backend, writes, specs };
|
|
239
|
+
return { ok: true, confirmed, backend: target.backend, writes, specs };
|
|
230
240
|
// AppleScript backends: guard on the backend's own availability, but only for a
|
|
231
241
|
// LOCAL run (a remote host is assumed to have the app — its ssh leg reports failure).
|
|
232
242
|
if (target.backend === 'iterm' || target.backend === 'ghostty') {
|
|
@@ -234,14 +244,14 @@ export async function injectIntoTerminal(target, text, opts = {}) {
|
|
|
234
244
|
const backend = target.backend === 'iterm' ? itermBackend : ghosttyBackend;
|
|
235
245
|
const ctx = opts.ctx ?? currentContext();
|
|
236
246
|
if (!backend.isAvailable(ctx)) {
|
|
237
|
-
return { ok: false, backend: target.backend, writes: 0, specs, error: `${backend.label} is not available here (platform ${ctx.platform})` };
|
|
247
|
+
return { ok: false, confirmed: false, backend: target.backend, writes: 0, specs, error: `${backend.label} is not available here (platform ${ctx.platform})` };
|
|
238
248
|
}
|
|
239
249
|
}
|
|
240
250
|
}
|
|
241
251
|
for (const spec of specs) {
|
|
242
252
|
const res = await runSpec(spec, opts.host, opts.resolveHost);
|
|
243
253
|
if (!res.ok)
|
|
244
|
-
return { ok: false, backend: target.backend, writes: 0, specs, error: res.error };
|
|
254
|
+
return { ok: false, confirmed: false, backend: target.backend, writes: 0, specs, error: res.error };
|
|
245
255
|
}
|
|
246
|
-
return { ok: true, backend: target.backend, writes, specs };
|
|
256
|
+
return { ok: true, confirmed, backend: target.backend, writes, specs };
|
|
247
257
|
}
|