Skip to content
Merged
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
29 changes: 29 additions & 0 deletions ts/docs/architecture/telemetry/local-telemetry-debugging.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,24 @@ In TypeAgent, run:

The @trace typeagent\* command enables all debug logs in the typeagent namespace.
The @log profile diagnostic command enables all structured + debug logs to be captured locally.
After sending a request, jump straight to its trace in Grafana Explore:

```
@log open last
```

To open a specific trace by id (for example one copied from a JSONL record or
a colleague's bug report):

```
@log open 0123456789abcdef0123456789abcdef
```

`@log open` checks the local Grafana endpoint and waits briefly for the exact
trace to become queryable in Tempo before launching the browser. A stopped
stack fails fast with a clear "start with `pnpm run telemetry:grafana`"
message, and a trace that was not captured reports an error instead of opening
an empty Explore view.

For setup details, queries, cleanup, and troubleshooting, continue below.

Expand Down Expand Up @@ -204,6 +222,17 @@ In Grafana:
2. Select the **Tempo** data source.
3. Search for the trace ID.

From TypeAgent, you can skip the manual Explore steps and jump straight to
the trace:

```
@log open <trace-id> # opens a specific trace
@log open last # opens the previous completed request's trace
```

The natural-language forms `open trace <id> in local Grafana`, `open last
trace`, and `view the last action result in Grafana` map to the same action.

If you do not have the trace ID, search for service
`typeagent-local` and narrow the time range to when you sent the request.

Expand Down
29 changes: 24 additions & 5 deletions ts/packages/dispatcher/dispatcher/src/command/command.ts
Original file line number Diff line number Diff line change
Expand Up @@ -505,6 +505,7 @@ export async function processCommand(
options?: ProcessCommandOptions,
parentContext?: Context,
): Promise<CommandResult | undefined> {
const isCommand = originalInput.trimStart().startsWith("@");
// Create the AbortController *before* acquiring the lock so that a
// cancelCommandByClientId() call that arrives while we are queued can
// already abort the controller that will drive this command.
Expand All @@ -521,8 +522,9 @@ export async function processCommand(
// steps in later phases; the root span carries only the values known
// at the outermost async boundary. Everything the wrapper receives is
// an identifier, not user text - see setTypeAgentSpanAttributes.
const sessionId = context.session.sessionDirPath
? getSessionName(context.session.sessionDirPath)
const sessionAtStart = context.session;
const sessionId = sessionAtStart.sessionDirPath
? getSessionName(sessionAtStart.sessionDirPath)
: undefined;
const rootAttributes: {
-readonly [K in keyof otel.TypeAgentSpanAttributes]: otel.TypeAgentSpanAttributes[K];
Expand Down Expand Up @@ -550,9 +552,7 @@ export async function processCommand(
...(requestId.connectionId === undefined
? {}
: { connectionId: requestId.connectionId }),
kind: originalInput.trimStart().startsWith("@")
? "command"
: "request",
kind: isCommand ? "command" : "request",
attachmentCount: attachments?.length ?? 0,
});
try {
Expand Down Expand Up @@ -602,6 +602,25 @@ export async function processCommand(
if (result !== undefined && rootTraceId !== undefined) {
result.traceId = rootTraceId;
}
if (
rootTraceId !== undefined &&
context.session === sessionAtStart
) {
context.sessionTraceHistory.push({
traceId: rootTraceId,
requestId: requestId.requestId,
kind: isCommand ? "command" : "request",
isTraceOpen:
result?.actions?.some(
(action) =>
action.schemaName ===
"system.log" &&
action.actionName ===
"openLogTrace",
) === true,
completedAt: Date.now(),
});
}
logRequestCompleted(
context.logger,
requestId.requestId,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -302,6 +302,14 @@ export type PendingTopicalRoute = {
};

// Command Handler Context definition.
export type SessionTrace = {
traceId: string;
requestId: string;
kind: "command" | "request";
isTraceOpen: boolean;
completedAt: number;
};

export type CommandHandlerContext = {
readonly agents: AppAgentManager;
readonly portRegistrar: IPortRegistrar;
Expand Down Expand Up @@ -406,6 +414,11 @@ export type CommandHandlerContext = {
* `typeagent.trace.id` so existing logs can still be joined.
*/
readonly traceId: string | undefined;
/**
* Canonical OpenTelemetry root traces completed in the current session.
* Kept in completion order for trace inspection commands.
*/
readonly sessionTraceHistory: SessionTrace[];
readonly telemetryOptions: {
readonly joinActiveTrace: boolean;
};
Expand Down Expand Up @@ -1330,6 +1343,7 @@ export async function initializeCommandHandlerContext(
logger,
activationId,
traceId,
sessionTraceHistory: [],
telemetryOptions: {
joinActiveTrace: options?.telemetry?.joinActiveTrace ?? false,
},
Expand Down Expand Up @@ -1983,6 +1997,7 @@ export async function setSessionOnCommandHandlerContext(
session: Session,
) {
context.session = session;
context.sessionTraceHistory.length = 0;
await context.agents.close();

await initializeMemory(context, session.getSessionDirPath());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,16 +3,18 @@

import type { ActionContext, TypeAgentAction } from "@typeagent/agent-sdk";

import type { CommandHandlerContext } from "../../commandHandlerContext.js";
import {
clearLogSettings,
openLogTrace,
setLogProfile,
showLogStatus,
} from "../handlers/logCommandHandler.js";
import type { LogAction } from "../schema/logActionSchema.js";

export async function executeLogAction(
action: TypeAgentAction<LogAction>,
context: ActionContext<unknown>,
context: ActionContext<CommandHandlerContext>,
) {
switch (action.actionName) {
case "showLogStatus":
Expand All @@ -24,6 +26,9 @@ export async function executeLogAction(
case "clearLogSettings":
clearLogSettings(context);
return;
case "openLogTrace":
await openLogTrace(action.parameters.traceId, context);
return;
default:
throw new Error(
`Invalid log action: ${(action as TypeAgentAction).actionName}`,
Expand Down
Loading
Loading