@phnx-labs/agents-cli 1.22.52 → 1.22.54
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 +336 -0
- package/README.md +42 -9
- package/dist/bootstrap.js +55 -154
- package/dist/cli/command-registry.d.ts +5 -0
- package/dist/cli/command-registry.js +8 -1
- package/dist/commands/accounts.js +220 -174
- package/dist/commands/apply.js +6 -3
- package/dist/commands/auth-mint.d.ts +8 -0
- package/dist/commands/auth-mint.js +96 -0
- package/dist/commands/auth.js +5 -1
- package/dist/commands/browser.js +1 -1
- package/dist/commands/cost.js +8 -2
- package/dist/commands/daemon.js +2 -2
- package/dist/commands/doctor.js +6 -1
- package/dist/commands/exec.js +26 -17
- package/dist/commands/fleet-capture.js +7 -0
- package/dist/commands/focus.d.ts +1 -0
- package/dist/commands/focus.js +4 -2
- package/dist/commands/go.d.ts +5 -4
- package/dist/commands/go.js +8 -7
- package/dist/commands/insights.js +9 -0
- package/dist/commands/monitors.js +85 -30
- package/dist/commands/output.js +8 -2
- package/dist/commands/repo.js +18 -0
- package/dist/commands/secrets.js +33 -14
- package/dist/commands/sessions-inject.js +8 -3
- package/dist/commands/sessions-picker.js +2 -1
- package/dist/commands/sessions.d.ts +20 -12
- package/dist/commands/sessions.js +94 -40
- package/dist/commands/setup-accounts.d.ts +8 -0
- package/dist/commands/setup-accounts.js +47 -0
- package/dist/commands/setup.d.ts +1 -1
- package/dist/commands/setup.js +11 -2
- package/dist/commands/share.d.ts +52 -3
- package/dist/commands/share.js +262 -18
- package/dist/commands/ssh.d.ts +7 -0
- package/dist/commands/ssh.js +53 -14
- package/dist/commands/status.js +14 -0
- package/dist/commands/sync.js +44 -0
- package/dist/commands/view.d.ts +3 -1
- package/dist/commands/view.js +5 -4
- package/dist/lib/account-registry.d.ts +15 -5
- package/dist/lib/account-registry.js +165 -53
- package/dist/lib/accounting/rotate.d.ts +20 -6
- package/dist/lib/accounting/rotate.js +38 -7
- package/dist/lib/accounting/usage.d.ts +37 -1
- package/dist/lib/accounting/usage.js +71 -6
- package/dist/lib/agent-spec/agents.d.ts +5 -2
- package/dist/lib/agent-spec/agents.js +25 -7
- package/dist/lib/analytics/mix-commands.js +12 -6
- package/dist/lib/answer-router.js +2 -1
- package/dist/lib/auth-mint.d.ts +150 -0
- package/dist/lib/auth-mint.js +434 -0
- package/dist/lib/browser/profiles.d.ts +18 -0
- package/dist/lib/browser/profiles.js +26 -1
- package/dist/lib/browser/registry.d.ts +44 -14
- package/dist/lib/browser/registry.js +141 -45
- package/dist/lib/browser/remote-control.d.ts +9 -7
- package/dist/lib/browser/remote-control.js +9 -7
- package/dist/lib/claude-account-token.d.ts +10 -0
- package/dist/lib/claude-account-token.js +14 -4
- package/dist/lib/config-drift.d.ts +37 -0
- package/dist/lib/config-drift.js +72 -0
- package/dist/lib/daemon/auth-sync-service.d.ts +19 -0
- package/dist/lib/daemon/auth-sync-service.js +34 -0
- package/dist/lib/daemon/daemon.js +30 -4
- package/dist/lib/daemon/runner.js +10 -2
- package/dist/lib/daemon-services.d.ts +1 -1
- package/dist/lib/daemon-services.js +5 -0
- package/dist/lib/device-config.d.ts +3 -3
- package/dist/lib/device-config.js +8 -7
- package/dist/lib/devices/config-migration.js +147 -1
- package/dist/lib/devices/connect.d.ts +26 -0
- package/dist/lib/devices/connect.js +48 -1
- package/dist/lib/devices/device-docs.d.ts +35 -0
- package/dist/lib/devices/device-docs.js +163 -0
- package/dist/lib/devices/discovery-policy.d.ts +14 -2
- package/dist/lib/devices/discovery-policy.js +31 -21
- package/dist/lib/devices/doctor-findings.d.ts +5 -1
- package/dist/lib/devices/doctor-findings.js +19 -1
- package/dist/lib/devices/registry.d.ts +11 -5
- package/dist/lib/devices/registry.js +46 -18
- package/dist/lib/exec.d.ts +88 -30
- package/dist/lib/exec.js +138 -34
- package/dist/lib/feed/feed.d.ts +10 -2
- package/dist/lib/feed/feed.js +35 -2
- package/dist/lib/feed-broadcast.js +1 -1
- package/dist/lib/fleet/apply.d.ts +11 -0
- package/dist/lib/fleet/apply.js +23 -3
- package/dist/lib/fleet/auth-sync.js +5 -3
- package/dist/lib/help.d.ts +9 -0
- package/dist/lib/help.js +29 -1
- package/dist/lib/hosts/dispatch.d.ts +4 -3
- package/dist/lib/hosts/dispatch.js +12 -8
- package/dist/lib/hosts/passthrough.d.ts +1 -10
- package/dist/lib/hosts/passthrough.js +1 -13
- package/dist/lib/hosts/providers/local.d.ts +9 -3
- package/dist/lib/hosts/providers/local.js +23 -12
- package/dist/lib/hosts/reconnect.d.ts +7 -4
- package/dist/lib/hosts/reconnect.js +29 -25
- package/dist/lib/hosts/registry.js +4 -1
- package/dist/lib/hosts/remote-os.js +3 -1
- package/dist/lib/installations/versions.js +9 -1
- package/dist/lib/linux-userns.d.ts +58 -0
- package/dist/lib/linux-userns.js +116 -0
- package/dist/lib/memory.d.ts +26 -0
- package/dist/lib/memory.js +80 -1
- package/dist/lib/monitors/config.d.ts +11 -0
- package/dist/lib/monitors/config.js +8 -0
- package/dist/lib/monitors/engine.js +8 -1
- package/dist/lib/monitors/state.d.ts +37 -1
- package/dist/lib/monitors/state.js +79 -4
- package/dist/lib/permissions-registry.d.ts +2 -0
- package/dist/lib/permissions-registry.js +116 -14
- package/dist/lib/permissions.d.ts +5 -3
- package/dist/lib/permissions.js +25 -27
- package/dist/lib/profiles.d.ts +8 -7
- package/dist/lib/profiles.js +12 -0
- package/dist/lib/project-key.d.ts +9 -0
- package/dist/lib/project-key.js +11 -0
- package/dist/lib/secrets/bundles.d.ts +35 -0
- package/dist/lib/secrets/bundles.js +78 -1
- package/dist/lib/secrets/push.d.ts +3 -8
- package/dist/lib/secrets/push.js +18 -14
- package/dist/lib/secrets/remote.d.ts +9 -18
- package/dist/lib/secrets/remote.js +11 -26
- package/dist/lib/secrets/reserved-sync.d.ts +65 -0
- package/dist/lib/secrets/reserved-sync.js +129 -0
- package/dist/lib/self-heal/checks/hook-manifest.d.ts +2 -0
- package/dist/lib/self-heal/checks/hook-manifest.js +56 -0
- package/dist/lib/self-heal/registry.js +4 -0
- package/dist/lib/self-heal/types.d.ts +1 -1
- package/dist/lib/session/active.d.ts +10 -1
- package/dist/lib/session/active.js +8 -5
- package/dist/lib/session/actor-sidecar.d.ts +7 -0
- package/dist/lib/session/actor-sidecar.js +2 -0
- package/dist/lib/session/db.d.ts +39 -4
- package/dist/lib/session/db.js +168 -31
- package/dist/lib/session/discover.d.ts +32 -4
- package/dist/lib/session/discover.js +126 -37
- package/dist/lib/session/insights.d.ts +14 -0
- package/dist/lib/session/insights.js +25 -2
- package/dist/lib/session/linear.js +1 -1
- package/dist/lib/session/live-metadata.js +1 -0
- package/dist/lib/session/pid-registry.d.ts +7 -0
- package/dist/lib/session/prompt.d.ts +15 -0
- package/dist/lib/session/prompt.js +21 -0
- package/dist/lib/session/shell-programs.d.ts +17 -0
- package/dist/lib/session/shell-programs.js +21 -0
- package/dist/lib/session/state.js +2 -1
- package/dist/lib/session/stream-render.js +2 -1
- package/dist/lib/session/tool-calls.js +2 -5
- package/dist/lib/session/trajectory-html.js +2 -1
- package/dist/lib/session/trajectory.js +3 -12
- package/dist/lib/session/types.d.ts +25 -0
- package/dist/lib/session/types.js +10 -0
- package/dist/lib/share/publish.d.ts +53 -5
- package/dist/lib/share/publish.js +99 -17
- package/dist/lib/share/worker-template.js +594 -64
- package/dist/lib/startup/root-command.js +2 -1
- package/dist/lib/state.d.ts +24 -0
- package/dist/lib/state.js +318 -54
- package/dist/lib/sync-status.d.ts +4 -0
- package/dist/lib/sync-status.js +3 -0
- package/dist/lib/terminal/resolve.d.ts +7 -0
- package/dist/lib/terminal/resolve.js +41 -2
- package/dist/lib/traces/classify.js +24 -19
- package/dist/lib/traces/insights.d.ts +67 -0
- package/dist/lib/traces/insights.js +178 -0
- package/dist/lib/traces/phenotype.d.ts +67 -0
- package/dist/lib/traces/phenotype.js +437 -0
- package/dist/lib/traces/segments.d.ts +133 -0
- package/dist/lib/traces/segments.js +301 -0
- package/dist/lib/traces/sync.d.ts +33 -0
- package/dist/lib/traces/sync.js +11 -2
- package/dist/lib/types.d.ts +47 -1
- package/dist/lib/usage-refresh.js +2 -1
- package/dist/lib/view-types.d.ts +2 -0
- package/dist/lib/watchdog/runner.js +18 -4
- package/package.json +2 -1
|
@@ -85,6 +85,20 @@ export interface InsightFacets {
|
|
|
85
85
|
assistantTurns: number;
|
|
86
86
|
toolCount: number;
|
|
87
87
|
errorCount: number;
|
|
88
|
+
/**
|
|
89
|
+
* Shell invocations bucketed by the actual executable + first subcommand token, not
|
|
90
|
+
* lumped under the harness tool name. `bucketKey` collapses each command to
|
|
91
|
+
* `git commit`, `gh pr`, `agents ssh`, `find`, `ssh→git pull`, … so the tool mix says
|
|
92
|
+
* which binary ran, not just that `Bash` did. Cross-harness: keyed on `e.command`,
|
|
93
|
+
* which the codex parser sets for `exec_command` too, so it is not Claude-`Bash`-only.
|
|
94
|
+
*/
|
|
95
|
+
bashCommands: Record<string, number>;
|
|
96
|
+
/**
|
|
97
|
+
* Same bucketing for the shell command that was running when a tool call failed —
|
|
98
|
+
* turns one `failed tool loop: Bash` line into per-binary failure counts
|
|
99
|
+
* (`git reconcile`, `find`, `gh`) so the friction is attributable to a command.
|
|
100
|
+
*/
|
|
101
|
+
bashCommandFailures: Record<string, number>;
|
|
88
102
|
/** Deterministic evidence buckets used by the actions-forward report. */
|
|
89
103
|
frictionSignals: Record<string, number>;
|
|
90
104
|
correctionSignals: Record<string, number>;
|
|
@@ -22,6 +22,7 @@
|
|
|
22
22
|
*/
|
|
23
23
|
import { computeSummaryStats, shortenModel } from './render.js';
|
|
24
24
|
import { classifyFileChanges, EDIT_TOOLS, WRITE_TOOLS } from './digest.js';
|
|
25
|
+
import { bucketKey } from './bash-command.js';
|
|
25
26
|
/** File extension → language label. Mirrors the set `/insights` attributes by. */
|
|
26
27
|
const LANGUAGE_BY_EXT = {
|
|
27
28
|
'.ts': 'TypeScript', '.tsx': 'TypeScript', '.mts': 'TypeScript', '.cts': 'TypeScript',
|
|
@@ -82,6 +83,7 @@ function emptyFacets() {
|
|
|
82
83
|
shellCommandsSeen: 0,
|
|
83
84
|
messageHours: new Array(24).fill(0), userTurns: 0, assistantTurns: 0,
|
|
84
85
|
toolCount: 0, errorCount: 0,
|
|
86
|
+
bashCommands: {}, bashCommandFailures: {},
|
|
85
87
|
frictionSignals: {}, correctionSignals: {}, automationSignals: {},
|
|
86
88
|
};
|
|
87
89
|
}
|
|
@@ -172,6 +174,9 @@ export function computeInsightFacets(events, timezoneOffsetMinutes = new Date().
|
|
|
172
174
|
/** Shortened model id of the last assistant activity (for stall attribution). */
|
|
173
175
|
let lastAssistantModel = null;
|
|
174
176
|
let lastFailedTool = null;
|
|
177
|
+
// Most recent shell command seen per tool, so an `error` event (which carries the
|
|
178
|
+
// failing tool name but not its command) can attribute the failure to a binary.
|
|
179
|
+
const lastCommandByTool = {};
|
|
175
180
|
for (const e of events) {
|
|
176
181
|
const ts = new Date(e.timestamp).getTime();
|
|
177
182
|
const hasTs = !Number.isNaN(ts);
|
|
@@ -191,16 +196,27 @@ export function computeInsightFacets(events, timezoneOffsetMinutes = new Date().
|
|
|
191
196
|
lastAssistantModel = m;
|
|
192
197
|
}
|
|
193
198
|
break;
|
|
194
|
-
case 'error':
|
|
199
|
+
case 'error': {
|
|
195
200
|
bump(f.errorCategories, categorizeError(e.content ?? e.output ?? ''));
|
|
196
201
|
if (e.tool && e.tool === lastFailedTool)
|
|
197
202
|
bump(f.frictionSignals, `failed tool loop: ${e.tool}`);
|
|
203
|
+
// Attribute the failure to the binary of that tool's most recent command,
|
|
204
|
+
// so `Bash` failures split into `git reconcile`/`find`/`gh` rather than one lump.
|
|
205
|
+
const failedCmd = e.tool ? lastCommandByTool[e.tool] : undefined;
|
|
206
|
+
if (failedCmd)
|
|
207
|
+
bump(f.bashCommandFailures, bucketKey(failedCmd));
|
|
198
208
|
lastFailedTool = e.tool ?? null;
|
|
199
209
|
classifyFriction(e.content ?? e.output ?? '', f.frictionSignals);
|
|
200
210
|
break;
|
|
211
|
+
}
|
|
201
212
|
case 'tool_result':
|
|
202
|
-
if (e.success !== false && e.outcome !== 'error')
|
|
213
|
+
if (e.success !== false && e.outcome !== 'error') {
|
|
203
214
|
lastFailedTool = null;
|
|
215
|
+
// Drop the succeeded tool's remembered command so a later failing call that
|
|
216
|
+
// carries no command of its own is not misattributed to this stale binary.
|
|
217
|
+
if (e.tool)
|
|
218
|
+
delete lastCommandByTool[e.tool];
|
|
219
|
+
}
|
|
204
220
|
break;
|
|
205
221
|
case 'message':
|
|
206
222
|
if (e.role === 'assistant') {
|
|
@@ -277,6 +293,9 @@ export function computeInsightFacets(events, timezoneOffsetMinutes = new Date().
|
|
|
277
293
|
f.gitCommits += countGitOp(e.command, 'commit');
|
|
278
294
|
f.gitPushes += countGitOp(e.command, 'push');
|
|
279
295
|
classifyAutomation(e.command, f.automationSignals);
|
|
296
|
+
bump(f.bashCommands, bucketKey(e.command));
|
|
297
|
+
if (toolName)
|
|
298
|
+
lastCommandByTool[toolName] = e.command;
|
|
280
299
|
}
|
|
281
300
|
break;
|
|
282
301
|
}
|
|
@@ -430,6 +449,10 @@ export function mergeFacets(into, add) {
|
|
|
430
449
|
bump(into.slashCommands, k, v);
|
|
431
450
|
for (const [k, v] of Object.entries(add.errorCategories))
|
|
432
451
|
bump(into.errorCategories, k, v);
|
|
452
|
+
for (const [k, v] of Object.entries(add.bashCommands ?? {}))
|
|
453
|
+
bump(into.bashCommands, k, v);
|
|
454
|
+
for (const [k, v] of Object.entries(add.bashCommandFailures ?? {}))
|
|
455
|
+
bump(into.bashCommandFailures, k, v);
|
|
433
456
|
for (const [k, v] of Object.entries(add.frictionSignals ?? {}))
|
|
434
457
|
bump(into.frictionSignals, k, v);
|
|
435
458
|
for (const [k, v] of Object.entries(add.correctionSignals ?? {}))
|
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
* 2. `workspaceUrlKey` in the linear-cli config (`~/.linear-cli/config.json`).
|
|
10
10
|
*
|
|
11
11
|
* When neither is set the ticket stays plain text — it's still shown, just not
|
|
12
|
-
* linked. This keeps the
|
|
12
|
+
* linked. This keeps the published CLI free of any Rush/workspace-specific string.
|
|
13
13
|
*/
|
|
14
14
|
import fs from 'node:fs';
|
|
15
15
|
import os from 'node:os';
|
|
@@ -37,6 +37,7 @@ export function activeSessionToSessionMeta(active, self, nowMs) {
|
|
|
37
37
|
id,
|
|
38
38
|
shortId: deriveShortId(id),
|
|
39
39
|
agent: active.kind,
|
|
40
|
+
harness: active.harness,
|
|
40
41
|
timestamp: new Date(startedMs).toISOString(),
|
|
41
42
|
lastActivity: new Date(lastMs).toISOString(),
|
|
42
43
|
// An empty file path is honest — a just-created session may have no
|
|
@@ -1,6 +1,13 @@
|
|
|
1
1
|
export interface PidSessionEntry {
|
|
2
2
|
pid: number;
|
|
3
3
|
agent: string;
|
|
4
|
+
/**
|
|
5
|
+
* Custom harness / profile name when this pid was launched via
|
|
6
|
+
* `agents run <profile>` (e.g. `deepseek`). `agent` stays the HOST
|
|
7
|
+
* CLI (`claude`) so process matching and transcript discovery keep
|
|
8
|
+
* working. `sessions --active` displays this when set (PHNX-2935).
|
|
9
|
+
*/
|
|
10
|
+
harness?: string;
|
|
4
11
|
/** The launch session id. Present for agents launched with a known id (Claude). */
|
|
5
12
|
sessionId?: string;
|
|
6
13
|
cwd?: string;
|
|
@@ -69,5 +69,20 @@ export interface ClassifiedPrompt {
|
|
|
69
69
|
export declare function classifyUserPrompt(raw: string | undefined, opts?: {
|
|
70
70
|
hasImageAttachment?: boolean;
|
|
71
71
|
}): ClassifiedPrompt;
|
|
72
|
+
/**
|
|
73
|
+
* Collapse a harness-generated session title when it is injected skill
|
|
74
|
+
* scaffolding; otherwise leave the title as the harness wrote it.
|
|
75
|
+
*
|
|
76
|
+
* Claude's `ai-title` and Cursor's `chatMeta.title` are both derived from the
|
|
77
|
+
* first turn, so a session opened with a skill gets named after the injected
|
|
78
|
+
* "Base directory for this skill: …" line. {@link classifyUserPrompt} reports
|
|
79
|
+
* `kind: 'skill'` only for that line, so collapse it to `/<skill>`. Empty or
|
|
80
|
+
* whitespace-only input yields `undefined` so the caller falls through to the
|
|
81
|
+
* first-prompt topic.
|
|
82
|
+
*
|
|
83
|
+
* A user-authored title (Claude `/rename` / `custom-title`) is never passed
|
|
84
|
+
* here — the caller keeps it verbatim.
|
|
85
|
+
*/
|
|
86
|
+
export declare function cleanGeneratedSessionLabel(title: string | undefined): string | undefined;
|
|
72
87
|
/** Extract a one-line topic from a raw user message, or undefined if the message is pure noise. */
|
|
73
88
|
export declare function extractSessionTopic(raw: string): string | undefined;
|
|
@@ -201,6 +201,27 @@ export function classifyUserPrompt(raw, opts = {}) {
|
|
|
201
201
|
const display = (cleanedFirst || firstLine).replace(IMAGE_PATH_RE, '[image]').trim();
|
|
202
202
|
return { clean: display, kind: 'text' };
|
|
203
203
|
}
|
|
204
|
+
/**
|
|
205
|
+
* Collapse a harness-generated session title when it is injected skill
|
|
206
|
+
* scaffolding; otherwise leave the title as the harness wrote it.
|
|
207
|
+
*
|
|
208
|
+
* Claude's `ai-title` and Cursor's `chatMeta.title` are both derived from the
|
|
209
|
+
* first turn, so a session opened with a skill gets named after the injected
|
|
210
|
+
* "Base directory for this skill: …" line. {@link classifyUserPrompt} reports
|
|
211
|
+
* `kind: 'skill'` only for that line, so collapse it to `/<skill>`. Empty or
|
|
212
|
+
* whitespace-only input yields `undefined` so the caller falls through to the
|
|
213
|
+
* first-prompt topic.
|
|
214
|
+
*
|
|
215
|
+
* A user-authored title (Claude `/rename` / `custom-title`) is never passed
|
|
216
|
+
* here — the caller keeps it verbatim.
|
|
217
|
+
*/
|
|
218
|
+
export function cleanGeneratedSessionLabel(title) {
|
|
219
|
+
const trimmed = title?.trim();
|
|
220
|
+
if (!trimmed)
|
|
221
|
+
return undefined;
|
|
222
|
+
const classified = classifyUserPrompt(trimmed);
|
|
223
|
+
return classified.kind === 'skill' ? classified.clean : trimmed;
|
|
224
|
+
}
|
|
204
225
|
/** Extract a one-line topic from a raw user message, or undefined if the message is pure noise. */
|
|
205
226
|
export function extractSessionTopic(raw) {
|
|
206
227
|
if (!raw.trim())
|
|
@@ -9,6 +9,23 @@ export interface ShellProgramOccurrence {
|
|
|
9
9
|
program: string;
|
|
10
10
|
role: ShellProgramRole;
|
|
11
11
|
}
|
|
12
|
+
/**
|
|
13
|
+
* Harness tool NAMES that run a shell command — Claude's `Bash`, Codex's
|
|
14
|
+
* `exec_command`/`exec` (`exec` is newer gpt-5.6-sol), Droid's `Execute`, plus the
|
|
15
|
+
* generic `shell`/`run_shell_command`/`run_command`/`execute`. This is the single
|
|
16
|
+
* source of truth for "does this tool row carry a shell command?", consumed by the
|
|
17
|
+
* trajectory model (program resolution + command-arg extraction), the directory-touched
|
|
18
|
+
* scan in `state.ts`, the tool-call indexer, and stream rendering — replacing five
|
|
19
|
+
* hand-synced copies that had already drifted apart.
|
|
20
|
+
*
|
|
21
|
+
* Distinct from {@link SHELL_WRAPPERS}, which is interpreter binaries (`bash`, `sh`…)
|
|
22
|
+
* found INSIDE a command string, not the harness's tool name. Match through
|
|
23
|
+
* {@link isShellExecTool} so casing is normalized in one place — harnesses disagree on
|
|
24
|
+
* case (`Bash` vs `bash`, `Execute` vs `exec_command`).
|
|
25
|
+
*/
|
|
26
|
+
export declare const SHELL_EXEC_TOOLS: Set<string>;
|
|
27
|
+
/** Case-insensitive membership test for {@link SHELL_EXEC_TOOLS}. */
|
|
28
|
+
export declare function isShellExecTool(tool: string | undefined): boolean;
|
|
12
29
|
/** Return a word only when Bash expansion cannot change its value. */
|
|
13
30
|
export declare function staticShellWord(word: Word | undefined): string | undefined;
|
|
14
31
|
/** Parse Bash without executing it and return every statically identifiable program. */
|
|
@@ -1,5 +1,26 @@
|
|
|
1
1
|
import path from 'path';
|
|
2
2
|
import { parse, } from 'unbash';
|
|
3
|
+
/**
|
|
4
|
+
* Harness tool NAMES that run a shell command — Claude's `Bash`, Codex's
|
|
5
|
+
* `exec_command`/`exec` (`exec` is newer gpt-5.6-sol), Droid's `Execute`, plus the
|
|
6
|
+
* generic `shell`/`run_shell_command`/`run_command`/`execute`. This is the single
|
|
7
|
+
* source of truth for "does this tool row carry a shell command?", consumed by the
|
|
8
|
+
* trajectory model (program resolution + command-arg extraction), the directory-touched
|
|
9
|
+
* scan in `state.ts`, the tool-call indexer, and stream rendering — replacing five
|
|
10
|
+
* hand-synced copies that had already drifted apart.
|
|
11
|
+
*
|
|
12
|
+
* Distinct from {@link SHELL_WRAPPERS}, which is interpreter binaries (`bash`, `sh`…)
|
|
13
|
+
* found INSIDE a command string, not the harness's tool name. Match through
|
|
14
|
+
* {@link isShellExecTool} so casing is normalized in one place — harnesses disagree on
|
|
15
|
+
* case (`Bash` vs `bash`, `Execute` vs `exec_command`).
|
|
16
|
+
*/
|
|
17
|
+
export const SHELL_EXEC_TOOLS = new Set([
|
|
18
|
+
'bash', 'exec', 'execute', 'exec_command', 'run_command', 'run_shell_command', 'shell',
|
|
19
|
+
]);
|
|
20
|
+
/** Case-insensitive membership test for {@link SHELL_EXEC_TOOLS}. */
|
|
21
|
+
export function isShellExecTool(tool) {
|
|
22
|
+
return tool !== undefined && SHELL_EXEC_TOOLS.has(tool.toLowerCase());
|
|
23
|
+
}
|
|
3
24
|
const SHELL_WRAPPERS = new Set(['bash', 'sh', 'zsh', 'dash', 'ksh']);
|
|
4
25
|
const PROGRAM_WRAPPERS = new Set(['command', 'builtin', 'env', 'nohup', 'sudo']);
|
|
5
26
|
const MAX_NESTED_DEPTH = 3;
|
|
@@ -16,6 +16,7 @@
|
|
|
16
16
|
*/
|
|
17
17
|
import * as path from 'path';
|
|
18
18
|
import { isCompletedTodoStatus, SNAPSHOT_TODO_TOOLS, summarizeToolUse } from './parse.js';
|
|
19
|
+
import { isShellExecTool } from './shell-programs.js';
|
|
19
20
|
/**
|
|
20
21
|
* Detect per-session rate-limit / usage-limit signals in assistant or error
|
|
21
22
|
* text (RUSH-1523). Matches the same shapes the ext's prewarm detectBlockingPrompt
|
|
@@ -174,7 +175,7 @@ export function extractRecentDirectoriesTouched(events, cwd) {
|
|
|
174
175
|
if (['Edit', 'Write', 'edit_file', 'write_file', 'create_file', 'edit', 'write'].includes(tool)) {
|
|
175
176
|
add(args.file_path ?? args.filePath ?? args.path ?? event.path, true);
|
|
176
177
|
}
|
|
177
|
-
else if (
|
|
178
|
+
else if (isShellExecTool(tool)) {
|
|
178
179
|
add(args.cwd ?? args.Cwd ?? args.workdir ?? args.working_directory ?? cwd);
|
|
179
180
|
}
|
|
180
181
|
}
|
|
@@ -2,6 +2,7 @@ import chalk from 'chalk';
|
|
|
2
2
|
import { truncate } from '../format.js';
|
|
3
3
|
import { parseClaudeContent, parseCodexContent, sanitizeEvents, summarizeToolUse } from './parse.js';
|
|
4
4
|
import { linkPath, relativeToCwd } from './render.js';
|
|
5
|
+
import { isShellExecTool } from './shell-programs.js';
|
|
5
6
|
const LINE_MAX = 120;
|
|
6
7
|
function timeOf(event) {
|
|
7
8
|
const d = new Date(event.timestamp);
|
|
@@ -11,7 +12,7 @@ function timeOf(event) {
|
|
|
11
12
|
}
|
|
12
13
|
function paintTool(tool) {
|
|
13
14
|
const label = tool.padEnd(10);
|
|
14
|
-
if (tool
|
|
15
|
+
if (isShellExecTool(tool))
|
|
15
16
|
return chalk.yellow(label);
|
|
16
17
|
if (tool === 'Edit' || tool === 'Write' || tool === 'Read')
|
|
17
18
|
return chalk.cyan(label);
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { createHash } from 'crypto';
|
|
2
2
|
import { parse } from 'acorn';
|
|
3
3
|
import { knownSecretValuesFromEnv, redactSecrets, sanitizeForTerminal } from '../redact.js';
|
|
4
|
-
import { extractShellPrograms } from './shell-programs.js';
|
|
4
|
+
import { extractShellPrograms, isShellExecTool } from './shell-programs.js';
|
|
5
5
|
export const TOOL_INPUT_MAX_BYTES = 16 * 1024;
|
|
6
6
|
export const TOOL_SUCCESS_OUTPUT_MAX_BYTES = 1024;
|
|
7
7
|
export const TOOL_ERROR_OUTPUT_MAX_BYTES = 4 * 1024;
|
|
@@ -13,9 +13,6 @@ export const TOOL_INDEX_LIMIT_ORDINAL = Number.MAX_SAFE_INTEGER;
|
|
|
13
13
|
export const TOOL_TEXT_PROCESSING_MAX_BYTES = 64 * 1024;
|
|
14
14
|
export const TOOL_SHELL_PARSE_MAX_BYTES = 64 * 1024;
|
|
15
15
|
export const TOOL_INDEX_VERSION = 7;
|
|
16
|
-
const SHELL_TOOLS = new Set([
|
|
17
|
-
'bash', 'exec', 'execute', 'exec_command', 'run_command', 'run_shell_command', 'shell',
|
|
18
|
-
]);
|
|
19
16
|
const BASE64_BLOCK = /(?:[A-Za-z0-9+/]{256,}={0,2})/g;
|
|
20
17
|
const SECRET_FIELD = /(?:token|secret|password|authorization|cookie|api[_-]?key|private[_-]?key)$/i;
|
|
21
18
|
const KNOWN_SECRET_VALUES = knownSecretValuesFromEnv();
|
|
@@ -225,7 +222,7 @@ export function commandsFromCodexExec(source) {
|
|
|
225
222
|
return commands;
|
|
226
223
|
}
|
|
227
224
|
function commandFor(tool, args, command) {
|
|
228
|
-
if (!
|
|
225
|
+
if (!isShellExecTool(tool))
|
|
229
226
|
return undefined;
|
|
230
227
|
const direct = command
|
|
231
228
|
?? (typeof args?.command === 'string' ? args.command : undefined)
|
|
@@ -14,6 +14,7 @@
|
|
|
14
14
|
*/
|
|
15
15
|
import { formatDuration, formatTokenCount } from './render.js';
|
|
16
16
|
import { escapeHtml } from './share-html.js';
|
|
17
|
+
import { isShellExecTool } from './shell-programs.js';
|
|
17
18
|
/**
|
|
18
19
|
* The honest footer prefix for a rendered trace: the redacted label only when
|
|
19
20
|
* something was actually redacted, else the "local only" disclaimer under
|
|
@@ -30,7 +31,7 @@ function toolColor(step) {
|
|
|
30
31
|
if (step.kind === 'thinking')
|
|
31
32
|
return '#3a3a55';
|
|
32
33
|
const tool = (step.tool ?? '').toLowerCase();
|
|
33
|
-
if (
|
|
34
|
+
if (isShellExecTool(tool))
|
|
34
35
|
return '#e0b341';
|
|
35
36
|
if (tool === 'read' || tool === 'grep' || tool === 'glob' || tool === 'search' || tool === 'codebase_search')
|
|
36
37
|
return '#4a9eff';
|
|
@@ -22,22 +22,13 @@
|
|
|
22
22
|
*/
|
|
23
23
|
import { redactSecrets } from '../redact.js';
|
|
24
24
|
import { computeSummaryStats } from './render.js';
|
|
25
|
-
import { extractShellPrograms } from './shell-programs.js';
|
|
25
|
+
import { extractShellPrograms, isShellExecTool } from './shell-programs.js';
|
|
26
26
|
const DEFAULT_IDLE_THRESHOLD_MS = 120_000;
|
|
27
27
|
const DEFAULT_MAX_STEPS = 5000;
|
|
28
28
|
const LABEL_MAX = 140;
|
|
29
29
|
const DETAIL_MAX = 400;
|
|
30
30
|
/** Tools that spawn an inline sub-agent inside THIS transcript (a `tool_use` row). */
|
|
31
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', 'exec', 'run_shell_command', 'shell', 'Execute']`)
|
|
36
|
-
* so Codex's `exec_command`/`exec` and the rest are covered, not just Claude's `Bash`.
|
|
37
|
-
* `exec` is newer Codex (gpt-5.6-sol) whose real command the parser unwraps from a
|
|
38
|
-
* JS cell into the event's `command` before this runs.
|
|
39
|
-
*/
|
|
40
|
-
const SHELL_TOOLS = new Set(['Bash', 'exec_command', 'exec', 'run_shell_command', 'shell', 'Execute']);
|
|
41
32
|
/**
|
|
42
33
|
* Shell builtins/assignments that are rarely the POINT of a command — the action
|
|
43
34
|
* is whatever runs after them (`export X=Y; git push` → `git`, `cd dir && bun test`
|
|
@@ -141,7 +132,7 @@ function stripLeadingShellNoise(command) {
|
|
|
141
132
|
function toolLabel(tool, args, command, redact, knownSecrets) {
|
|
142
133
|
let raw;
|
|
143
134
|
const lower = tool.toLowerCase();
|
|
144
|
-
if (
|
|
135
|
+
if (isShellExecTool(tool)) {
|
|
145
136
|
const cmd = command ?? stringArg(args, 'command', 'cmd', 'script');
|
|
146
137
|
raw = cmd ? stripLeadingShellNoise(cmd) : cmd;
|
|
147
138
|
}
|
|
@@ -247,7 +238,7 @@ export function buildTrajectory(events, meta, options = {}) {
|
|
|
247
238
|
label: toolLabel(tool, e.args, e.command, redact, knownSecrets),
|
|
248
239
|
delegation: INLINE_TASK_TOOLS.has(tool) ? 'inline-task' : undefined,
|
|
249
240
|
callId: e.callId,
|
|
250
|
-
program:
|
|
241
|
+
program: isShellExecTool(tool) ? effectiveProgram(e.command ?? (typeof e.args?.command === 'string' ? e.args.command : undefined)) : undefined,
|
|
251
242
|
exitCode: typeof e.exitCode === 'number' ? e.exitCode : undefined,
|
|
252
243
|
},
|
|
253
244
|
eventIndex: i,
|
|
@@ -31,6 +31,16 @@ export declare const AG_TMUX_NAME_RE: RegExp;
|
|
|
31
31
|
/** Whether `name` is an `ag-<agent>-<shortid>` tmux session name. */
|
|
32
32
|
export declare function isAgentTmuxAlias(name: string): boolean;
|
|
33
33
|
export declare function isSessionTrackedAgent(agent: string): agent is SessionAgentId;
|
|
34
|
+
/**
|
|
35
|
+
* The name `agents sessions` shows for a run. A custom harness launched via a
|
|
36
|
+
* profile (`agents run deepseek`) keeps its host agent for transcript discovery
|
|
37
|
+
* and parsing (`claude`) and stamps the profile name on `harness`. Display
|
|
38
|
+
* prefers that stamp so a deepseek run is not listed as claude (PHNX-2935).
|
|
39
|
+
*/
|
|
40
|
+
export declare function sessionDisplayAgent(session: {
|
|
41
|
+
agent: string;
|
|
42
|
+
harness?: string | null;
|
|
43
|
+
}): string;
|
|
34
44
|
/** A single normalized event within a session (message, tool call, thinking, etc.). */
|
|
35
45
|
export interface SessionEvent {
|
|
36
46
|
type: 'message' | 'tool_use' | 'tool_result' | 'thinking' | 'error' | 'init' | 'result' | 'usage' | 'attachment' | 'hook' | 'interrupt';
|
|
@@ -154,6 +164,13 @@ export interface SessionMeta {
|
|
|
154
164
|
id: string;
|
|
155
165
|
shortId: string;
|
|
156
166
|
agent: SessionAgentId;
|
|
167
|
+
/**
|
|
168
|
+
* Custom harness / profile name when this session was launched via
|
|
169
|
+
* `agents run <profile>` (e.g. `deepseek`). `agent` stays the HOST
|
|
170
|
+
* harness that produced the transcript (`claude`, …) so discovery and
|
|
171
|
+
* parsing keep working. Display surfaces this when set (PHNX-2935).
|
|
172
|
+
*/
|
|
173
|
+
harness?: string;
|
|
157
174
|
/** Where the indexed transcript came from. Routine rows are archived from a run directory. */
|
|
158
175
|
origin?: 'cli' | 'routine';
|
|
159
176
|
/** Routine name for transcripts archived from ~/.agents/.history/runs/<name>/<runId>/. */
|
|
@@ -367,6 +384,14 @@ export interface SessionMeta {
|
|
|
367
384
|
_matchedTerms?: string[];
|
|
368
385
|
/** BM25 relevance score from the most recent content-index search */
|
|
369
386
|
_bm25Score?: number;
|
|
387
|
+
/**
|
|
388
|
+
* A short highlighted excerpt (`**term**`) around the best-matching FTS5
|
|
389
|
+
* column (label/topic/project/user content/assistant answer) for the most
|
|
390
|
+
* recent content-index search. Unlike `_matchedTerms`/`_bm25Score` this is
|
|
391
|
+
* NOT stripped from `--json` output — it's the searchable-context payload a
|
|
392
|
+
* content-search result carries, not internal ranking bookkeeping.
|
|
393
|
+
*/
|
|
394
|
+
snippet?: string;
|
|
370
395
|
}
|
|
371
396
|
/** Output format for rendering a session's content. */
|
|
372
397
|
export type ViewMode = 'summary' | 'markdown' | 'json';
|
|
@@ -31,3 +31,13 @@ export function isAgentTmuxAlias(name) {
|
|
|
31
31
|
export function isSessionTrackedAgent(agent) {
|
|
32
32
|
return SESSION_AGENTS.includes(agent);
|
|
33
33
|
}
|
|
34
|
+
/**
|
|
35
|
+
* The name `agents sessions` shows for a run. A custom harness launched via a
|
|
36
|
+
* profile (`agents run deepseek`) keeps its host agent for transcript discovery
|
|
37
|
+
* and parsing (`claude`) and stamps the profile name on `harness`. Display
|
|
38
|
+
* prefers that stamp so a deepseek run is not listed as claude (PHNX-2935).
|
|
39
|
+
*/
|
|
40
|
+
export function sessionDisplayAgent(session) {
|
|
41
|
+
const harness = session.harness?.trim();
|
|
42
|
+
return harness || session.agent;
|
|
43
|
+
}
|
|
@@ -53,6 +53,12 @@ export interface PublishOptions {
|
|
|
53
53
|
writeToken?: string;
|
|
54
54
|
/** DI seam for tests — override `readSession()`. `null` means signed out. */
|
|
55
55
|
session?: import('../identity/client.js').PhoenixSession | null;
|
|
56
|
+
/**
|
|
57
|
+
* Override the sharer's avatar URL stamped on the object (test seam). When
|
|
58
|
+
* omitted it is derived from the signed-in email via {@link resolveShareAvatar};
|
|
59
|
+
* an empty string suppresses the stamp (initials-only bar).
|
|
60
|
+
*/
|
|
61
|
+
avatar?: string;
|
|
56
62
|
/** Force the BYO Cloudflare path even when signed in. */
|
|
57
63
|
byo?: boolean;
|
|
58
64
|
/** DI seam for tests — override the real HTTP PUT. */
|
|
@@ -83,6 +89,9 @@ export interface PublishOptions {
|
|
|
83
89
|
provenance?: ShareProvenance;
|
|
84
90
|
}
|
|
85
91
|
export type ShareVisibility = 'public' | 'unlisted' | 'me' | 'org';
|
|
92
|
+
/** The visibility levels a share may carry — the Worker's own set, used to
|
|
93
|
+
* validate `--visibility` and `share visibility <target> <level>`. */
|
|
94
|
+
export declare const SHARE_VISIBILITY_LEVELS: readonly ShareVisibility[];
|
|
86
95
|
export interface PublishResult {
|
|
87
96
|
url: string;
|
|
88
97
|
/** URL-safe object name, explicit (`--slug`) or deterministically derived. */
|
|
@@ -124,7 +133,7 @@ export interface ShareProvenance {
|
|
|
124
133
|
* provenance the CLI sets automatically, plus `expires-at` / `visibility` /
|
|
125
134
|
* `owner` which the Worker stamps itself.
|
|
126
135
|
*/
|
|
127
|
-
export declare const RESERVED_META_KEYS: readonly ["expires-at", "visibility", "owner", "agent", "session", "host", "repo", "date", "label", "label-source"];
|
|
136
|
+
export declare const RESERVED_META_KEYS: readonly ["expires-at", "published-at", "visibility", "owner", "org_domain", "agent", "session", "host", "repo", "date", "avatar", "label", "label-source"];
|
|
128
137
|
/**
|
|
129
138
|
* Auto-capture publish provenance from the exec env, git, and the local clock.
|
|
130
139
|
* Every field is present only when the environment genuinely carries it — a
|
|
@@ -139,6 +148,21 @@ export declare function resolveShareProvenance(opts?: {
|
|
|
139
148
|
dir?: string;
|
|
140
149
|
now?: Date;
|
|
141
150
|
}): ShareProvenance;
|
|
151
|
+
/**
|
|
152
|
+
* The sharer's avatar URL, stamped so the share bar can show a real profile
|
|
153
|
+
* picture instead of only the initials circle. We key a Gravatar on the SHA-256
|
|
154
|
+
* of the signed-in user's lowercased email (Gravatar resolves either MD5 or
|
|
155
|
+
* SHA-256), with `d=404` so Gravatar returns 404 for a user who has none — the
|
|
156
|
+
* bar's `<img>` onerror then falls back to the initials circle. Only the hash
|
|
157
|
+
* lands in public metadata, never the raw email. Returns '' when signed out
|
|
158
|
+
* (BYO without a Phoenix session), leaving the bar on the initials circle.
|
|
159
|
+
*
|
|
160
|
+
* `opts.session === null` means "explicitly signed out" (a test seam / BYO) and
|
|
161
|
+
* yields ''; `undefined` reads the real persisted session.
|
|
162
|
+
*/
|
|
163
|
+
export declare function resolveShareAvatar(opts?: {
|
|
164
|
+
session?: import('../identity/client.js').PhoenixSession | null;
|
|
165
|
+
}): string;
|
|
142
166
|
/**
|
|
143
167
|
* Parse repeated `--meta key=value` CLI args into a validated metadata record.
|
|
144
168
|
* Keys are lowercase `[a-z0-9-]`, up to 64 characters, and may not collide with
|
|
@@ -172,12 +196,36 @@ export declare function sanitizeLabel(text: string): string;
|
|
|
172
196
|
* The transliterations above cover what actually shows up; anything else outside
|
|
173
197
|
* latin1 is dropped, and a value that transliterates to nothing at all (a title
|
|
174
198
|
* written entirely in a non-latin script) degrades to a marker rather than an
|
|
175
|
-
* empty header.
|
|
176
|
-
*
|
|
177
|
-
*
|
|
178
|
-
*
|
|
199
|
+
* empty header. This value is the **latin1-safe floor** every Worker can read: a
|
|
200
|
+
* pre-Unicode Worker only ever sees `x-share-<field>`, so it MUST stay folded.
|
|
201
|
+
* Full Unicode rides ALONGSIDE it in a percent-encoded companion header
|
|
202
|
+
* (`toPercentHeaderValue` / {@link needsUnicodeCompanion}), which a new Worker
|
|
203
|
+
* opts into via `x-share-encoding: percent` and an old one ignores — so a
|
|
204
|
+
* Japanese/emoji title renders in full on an updated Worker and still folds
|
|
205
|
+
* gracefully everywhere else (PHNX-2786).
|
|
179
206
|
*/
|
|
180
207
|
export declare function toHeaderValue(text: string): string;
|
|
208
|
+
/**
|
|
209
|
+
* Whether text carries a code point above latin1 (U+00FF) — the meaningful
|
|
210
|
+
* display content `fetch`'s ByteString cannot hold, which {@link toHeaderValue}
|
|
211
|
+
* therefore transliterates or drops. This is the range worth carrying in the
|
|
212
|
+
* percent-encoded companion: an em dash, a curly quote, an emoji, or any
|
|
213
|
+
* CJK/Arabic/Hindi text. A plain accented latin1 name (`José`, é = U+00E9) is
|
|
214
|
+
* NOT lossy and needs no companion.
|
|
215
|
+
*
|
|
216
|
+
* `toHeaderValue` ALSO strips C0/C1 control characters (below U+0020, and
|
|
217
|
+
* U+007F–U+009F), which this deliberately does not flag: a raw ANSI/control
|
|
218
|
+
* sequence is not display text and must not be reconstructed into a page's
|
|
219
|
+
* rendered metadata, so it stays dropped on both the old and new Worker paths.
|
|
220
|
+
*/
|
|
221
|
+
export declare function needsUnicodeCompanion(text: string): boolean;
|
|
222
|
+
/**
|
|
223
|
+
* Percent-encode a single-line free-text value for the `x-share-<field>-u`
|
|
224
|
+
* companion header. Whitespace is collapsed first (matching the folded value's
|
|
225
|
+
* single-line shape), then `encodeURIComponent` makes it pure-ASCII and
|
|
226
|
+
* header-safe. The Worker recovers the original with `decodeURIComponent`.
|
|
227
|
+
*/
|
|
228
|
+
export declare function toPercentHeaderValue(text: string): string;
|
|
181
229
|
/**
|
|
182
230
|
* Best-effort human title when `--label` is omitted: the HTML `<title>`, else a
|
|
183
231
|
* Markdown frontmatter `title:`, else the filename. Always returns something —
|