Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions command-snapshot.json
Original file line number Diff line number Diff line change
Expand Up @@ -241,6 +241,14 @@
],
"plugin": "@salesforce/plugin-agent"
},
{
"alias": [],
"command": "agent:trace:list",
"flagAliases": [],
"flagChars": ["a"],
"flags": ["agent", "flags-dir", "json", "session-id", "since"],
"plugin": "@salesforce/plugin-agent"
},
{
"alias": [],
"command": "agent:validate:authoring-bundle",
Expand Down
87 changes: 87 additions & 0 deletions messages/agent.trace.list.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
# summary

List the trace files that were recorded during all agent preview sessions.

# description

Lists trace files recorded during agent preview sessions. By default, lists all traces for all agents and all of their sessions. Use flags to narrow results: filter by agent name (--agent), by session (--session-id), or by date (--since).

Each row in the output corresponds to one trace file, which in turn corresponds to one agent session. The Agent column shows the authoring bundle or API name used when starting the session.

# flags.agent.summary

Only show traces for this agent name (substring match). Matches against the name used when starting the session, whether that's an authoring bundle or a published agent API name.

# flags.session-id.summary

Session ID used to filter the list of trace files.

# flags.since.summary

Date used to filter the list of trace files; only those recorded on or after the date are listed.

# flags.since.description

Accepts ISO 8601 format: date-only (2026-04-20), date-time (2026-04-20T14:00:00Z), or date-time with milliseconds (2026-04-20T14:00:00.000Z). The "Recorded At" values shown in the table output are valid inputs.

# error.invalidSince

Invalid --since value: '%s'. Use ISO 8601 format — date-only (2026-04-20), date-time (2026-04-20T14:00:00Z), or with milliseconds (2026-04-20T14:00:00.000Z). The "Recorded At" values shown in the table output are valid inputs.

# output.empty

No trace files found.

# output.tableHeader.agent

Agent

# output.tableHeader.sessionId

Session ID

# output.tableHeader.planId

Plan ID

# output.tableHeader.mtime

Recorded At

# output.tableHeader.size

Size

# output.tableHeader.path

Path

# examples

- List all traces for all agents and sessions:

<%= config.bin %> <%= command.id %>

- List all traces for a specific agent:

<%= config.bin %> <%= command.id %> --agent My_Agent

- List traces for a specific session:

<%= config.bin %> <%= command.id %> --session-id <SESSION_ID>

- List traces recorded on or after April 20, 2026 (date-only, interpreted as UTC midnight):

<%= config.bin %> <%= command.id %> --since 2026-04-20

- List traces recorded on or after a specific UTC time:

<%= config.bin %> <%= command.id %> --since 2026-04-20T14:00:00Z

- Filter by agent and date together:

<%= config.bin %> <%= command.id %> --agent My_Agent --since 2026-04-20

- Return results as JSON:

<%= config.bin %> <%= command.id %> --json
34 changes: 34 additions & 0 deletions schemas/agent-trace-list.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
{
"$schema": "http://json-schema.org/draft-07/schema#",
"$ref": "#/definitions/AgentTraceListResult",
"definitions": {
"AgentTraceListResult": {
"type": "array",
"items": {
"type": "object",
"properties": {
"agent": {
"type": "string"
},
"sessionId": {
"type": "string"
},
"planId": {
"type": "string"
},
"path": {
"type": "string"
},
"size": {
"type": "number"
},
"mtime": {
"type": "string"
}
},
"required": ["agent", "sessionId", "planId", "path", "size", "mtime"],
"additionalProperties": false
}
}
}
}
126 changes: 126 additions & 0 deletions src/commands/agent/trace/list.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
/*
* Copyright 2026, Salesforce, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

import { Flags, SfCommand, toHelpSection } from '@salesforce/sf-plugins-core';
import { Messages, SfError } from '@salesforce/core';
import { listCachedPreviewSessions, listSessionTraces, type TraceFileInfo } from '@salesforce/agents';

Messages.importMessagesDirectoryFromMetaUrl(import.meta.url);
const messages = Messages.loadMessages('@salesforce/plugin-agent', 'agent.trace.list');

const ISO_8601 = /^\d{4}-\d{2}-\d{2}(T\d{2}:\d{2}:\d{2}(\.\d+)?Z)?$/;

export type AgentTraceListResult = Array<{
agent: string;
sessionId: string;
planId: string;
path: string;
size: number;
mtime: string;
}>;

export default class AgentTraceList extends SfCommand<AgentTraceListResult> {
public static readonly summary = messages.getMessage('summary');
public static readonly description = messages.getMessage('description');
public static readonly examples = messages.getMessages('examples');
public static readonly requiresProject = true;

public static readonly errorCodes = toHelpSection('ERROR CODES', {
'Succeeded (0)': 'Trace files listed successfully (or empty list if none found).',
});

public static readonly flags = {
'session-id': Flags.string({
summary: messages.getMessage('flags.session-id.summary'),
}),
agent: Flags.string({
summary: messages.getMessage('flags.agent.summary'),
char: 'a',
}),
since: Flags.custom<Date>({
summary: messages.getMessage('flags.since.summary'),
description: messages.getMessage('flags.since.description'),
// eslint-disable-next-line @typescript-eslint/require-await
parse: async (raw): Promise<Date> => {
if (!ISO_8601.test(raw)) {
throw new SfError(messages.getMessage('error.invalidSince', [raw]), 'InvalidDate');
}
const d = new Date(raw);
if (isNaN(d.getTime())) {
throw new SfError(messages.getMessage('error.invalidSince', [raw]), 'InvalidDate');
}
return d;
},
})(),
};

public async run(): Promise<AgentTraceListResult> {
const { flags } = await this.parse(AgentTraceList);

const agentNameFilter = flags.agent?.toLowerCase();

const cachedAgents = await listCachedPreviewSessions(this.project!);

const result: AgentTraceListResult = [];

for (const { agentId, displayName, sessions } of cachedAgents) {
if (agentNameFilter && !displayName?.toLowerCase().includes(agentNameFilter)) continue;

for (const { sessionId } of sessions) {
if (flags['session-id'] && sessionId !== flags['session-id']) continue;

// eslint-disable-next-line no-await-in-loop
let traces: TraceFileInfo[] = await listSessionTraces(agentId, sessionId);

if (flags.since) {
traces = traces.filter((t) => t.mtime >= flags.since!);
}

for (const t of traces) {
result.push({
agent: displayName ?? agentId,
sessionId,
planId: t.planId,
path: t.path,
size: t.size,
mtime: t.mtime.toISOString(),
});
}
}
}

if (result.length === 0) {
this.log(messages.getMessage('output.empty'));
return [];
}

if (!this.jsonEnabled()) {
this.table({
data: result.map((r) => ({ ...r, size: `${r.size}B` })),
columns: [
{ key: 'agent', name: messages.getMessage('output.tableHeader.agent') },
{ key: 'sessionId', name: messages.getMessage('output.tableHeader.sessionId') },
{ key: 'planId', name: messages.getMessage('output.tableHeader.planId') },
{ key: 'mtime', name: messages.getMessage('output.tableHeader.mtime') },
{ key: 'size', name: messages.getMessage('output.tableHeader.size') },
{ key: 'path', name: messages.getMessage('output.tableHeader.path') },
],
});
}

return result;
}
}
Loading
Loading