Skip to content

Commit 91b683f

Browse files
authored
refactor(aio): apply simplify-pass cleanups to telemetry and session state
Findings from a 4-angle simplify pass (reuse / simplification / efficiency / altitude), no behavior change except where noted: - Extract resolveEffectiveCloudStatus: the 9-line terminal-wins status precedence chain was duplicated byte-for-byte in deriveCloudRunState and deriveSessionViewState; three review angles flagged it independently. One exported helper in cloudRunState.ts now owns the policy. - isTaskDetailNotFoundError uses requestErrorStatus(error) === 404 instead of string-matching "[404]" in the message - the fetcher keeps that message format only as legacy, and the substring check could false-positive on a non-404 error quoting an upstream body. Behavior tightened accordingly (plain Errors mentioning [404] no longer read as 404s). - taskDetailQuery no longer retries 404s: the detail fetch is now always-on, and optimistic/cloud-pending tasks 404 by design, so react-query's default 3 retries turned every mount of a fresh task into 4 requests. - pickFreshestTask is binary (all six call sites were), which also deletes the logically dead ?? fallbacks at the two hook call sites. - sessionService: merge the duplicated optimistic-clear blocks and skip-apply early returns in performCloudTaskSessionHydration; shrink finalizeTerminalCloudTask to the (taskRunId, status) both call sites use; clearTerminalCloudPromptState derives taskId from the session (removing the dual-key lookup hazard) and now also serves the pre-existing inline terminal cleanup in handleCloudTaskUpdate, so the composer-settling invariant lives in one place; staleness is computed once per update in handleCloudTaskUpdate. - OTel modules: normalizeMethod (the safety-relevant __posthog/ double-prefix rule), asString, and the batch flush-interval constant move to otel-attributes.ts instead of being duplicated across otel-telemetry.ts and otel-trace-builder.ts. Generated-By: PostHog Code Task-Id: 0c511836-2180-455a-9b58-45df2a0661ec
1 parent 192126e commit 91b683f

10 files changed

Lines changed: 122 additions & 123 deletions

File tree

packages/agent/src/otel-attributes.ts

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,30 @@ const MAX_ATTR_CHARS = 200;
66
// session cleanup (the sandbox can be torn down right after), so keep it short.
77
const EXPORT_TIMEOUT_MS = 5000;
88

9+
// Batch flush cadence shared by the log and span processors.
10+
const DEFAULT_FLUSH_INTERVAL_MS = 2000;
11+
912
export type AttributeValue = string | number | boolean;
1013
export type Attributes = Record<string, AttributeValue>;
1114

12-
export { EXPORT_TIMEOUT_MS, MAX_ATTR_CHARS, MAX_BODY_CHARS };
15+
export {
16+
DEFAULT_FLUSH_INTERVAL_MS,
17+
EXPORT_TIMEOUT_MS,
18+
MAX_ATTR_CHARS,
19+
MAX_BODY_CHARS,
20+
};
21+
22+
/**
23+
* extNotification() can double-prefix custom methods (see matchesExt in
24+
* acp-extensions.ts); normalize so both spellings map identically.
25+
*/
26+
export function normalizeMethod(method: string): string {
27+
return method.startsWith("__posthog/") ? method.slice(1) : method;
28+
}
29+
30+
export function asString(value: unknown): string | undefined {
31+
return typeof value === "string" ? value : undefined;
32+
}
1333

1434
export function truncate(value: string, max: number): string {
1535
return value.length <= max ? value : `${value.slice(0, max)}…`;

packages/agent/src/otel-telemetry.ts

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -13,9 +13,12 @@ import { POSTHOG_NOTIFICATIONS } from "./acp-extensions";
1313
import {
1414
type Attributes,
1515
asRecord,
16+
asString,
17+
DEFAULT_FLUSH_INTERVAL_MS,
1618
EXPORT_TIMEOUT_MS,
1719
entryTime,
1820
MAX_BODY_CHARS,
21+
normalizeMethod,
1922
strAttr,
2023
truncate,
2124
usageAttributes,
@@ -26,7 +29,6 @@ import type { StoredNotification } from "./types";
2629
import type { Logger } from "./utils/logger";
2730

2831
const SERVICE_NAME = "posthog-code-agent";
29-
const DEFAULT_FLUSH_INTERVAL_MS = 2000;
3032

3133
export interface OtelTelemetryConfig {
3234
/** Full OTLP logs endpoint URL, e.g. "https://us.i.posthog.com/i/v1/logs" */
@@ -146,11 +148,7 @@ export function mapNotificationToLogRecord(
146148
): MappedLogRecord | null {
147149
const rawMethod = entry.notification.method;
148150
if (typeof rawMethod !== "string") return null;
149-
// extNotification() can double-prefix custom methods (see matchesExt in
150-
// acp-extensions.ts); normalize so both spellings map identically.
151-
const method = rawMethod.startsWith("__posthog/")
152-
? rawMethod.slice(1)
153-
: rawMethod;
151+
const method = normalizeMethod(rawMethod);
154152
const params = asRecord(entry.notification.params) ?? {};
155153

156154
if (method === "session/update") {
@@ -212,7 +210,7 @@ export function mapNotificationToLogRecord(
212210
strAttr(attrs, "progress_group", params.group);
213211
const step = strAttr(attrs, "progress_step", params.step);
214212
const status = strAttr(attrs, "progress_status", params.status);
215-
const label = typeof params.label === "string" ? params.label : undefined;
213+
const label = asString(params.label);
216214
return record(
217215
INFO,
218216
`progress: ${step ?? "step"} ${status ?? ""}${label ? ` (${label})` : ""}`.trim(),

packages/agent/src/otel-trace-builder.ts

Lines changed: 9 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -17,15 +17,16 @@ import { POSTHOG_NOTIFICATIONS } from "./acp-extensions";
1717
import {
1818
type Attributes,
1919
asRecord,
20+
asString,
21+
DEFAULT_FLUSH_INTERVAL_MS,
2022
EXPORT_TIMEOUT_MS,
2123
entryTime,
24+
normalizeMethod,
2225
strAttr,
2326
usageAttributes,
2427
} from "./otel-attributes";
2528
import type { StoredNotification } from "./types";
2629

27-
const DEFAULT_FLUSH_INTERVAL_MS = 2000;
28-
2930
export interface RunTraceBuilderConfig {
3031
/** Full OTLP traces endpoint URL, e.g. "https://us.i.posthog.com/i/v1/traces" */
3132
url: string;
@@ -98,9 +99,7 @@ export class RunTraceBuilder {
9899
if (this.ended) return this.rootContext;
99100
const rawMethod = entry.notification.method;
100101
if (typeof rawMethod !== "string") return this.currentContext();
101-
const method = rawMethod.startsWith("__posthog/")
102-
? rawMethod.slice(1)
103-
: rawMethod;
102+
const method = normalizeMethod(rawMethod);
104103
const params = asRecord(entry.notification.params) ?? {};
105104
const time = entryTime(entry.timestamp);
106105

@@ -181,8 +180,7 @@ export class RunTraceBuilder {
181180

182181
private endTurn(params: Record<string, unknown>, time: Date): Context {
183182
const context = this.turnContext ?? this.rootContext;
184-
const stopReason =
185-
typeof params.stopReason === "string" ? params.stopReason : undefined;
183+
const stopReason = asString(params.stopReason);
186184
this.closeOpenTools(time);
187185
this.closeTurn({ stopReason, errored: stopReason === "error" }, time);
188186
// The sandbox never emits task_complete for successful runs (the terminal
@@ -232,12 +230,11 @@ export class RunTraceBuilder {
232230
}
233231

234232
private startTool(update: Record<string, unknown>, time: Date): Context {
235-
const toolCallId =
236-
typeof update.toolCallId === "string" ? update.toolCallId : undefined;
233+
const toolCallId = asString(update.toolCallId);
237234
const existing = toolCallId ? this.toolSpans.get(toolCallId) : undefined;
238235
if (existing) return existing.context;
239236

240-
const kind = typeof update.kind === "string" ? update.kind : "unknown";
237+
const kind = asString(update.kind) ?? "unknown";
241238
const parentContext = this.turnContext ?? this.rootContext;
242239
const attributes: Attributes = { tool_kind: kind };
243240
if (toolCallId) attributes.tool_call_id = toolCallId;
@@ -263,8 +260,7 @@ export class RunTraceBuilder {
263260
update: Record<string, unknown>,
264261
time: Date,
265262
): Context {
266-
const toolCallId =
267-
typeof update.toolCallId === "string" ? update.toolCallId : undefined;
263+
const toolCallId = asString(update.toolCallId);
268264
const open = toolCallId ? this.toolSpans.get(toolCallId) : undefined;
269265
if (!open || !toolCallId) return this.currentContext();
270266

@@ -283,8 +279,7 @@ export class RunTraceBuilder {
283279
private handleError(params: Record<string, unknown>, time: Date): Context {
284280
this.rootErrored = true;
285281
this.closeOpenTools(time, { interrupted: true });
286-
const stopReason =
287-
typeof params.stopReason === "string" ? params.stopReason : undefined;
282+
const stopReason = asString(params.stopReason);
288283
this.closeTurn({ stopReason, errored: true }, time);
289284
// params.error is free text that can embed prompt or repo content, so
290285
// only the error's provenance is exported; the raw message stays in the

packages/core/src/sessions/sessionService.ts

Lines changed: 34 additions & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -5545,9 +5545,7 @@ export class SessionService {
55455545
runState,
55465546
);
55475547
}
5548-
this.finalizeTerminalCloudTask(taskId, taskRunId, {
5549-
status: runStatus,
5550-
});
5548+
this.finalizeTerminalCloudTask(taskRunId, runStatus);
55515549
this.stopCloudTaskWatch(taskId);
55525550
return () => {};
55535551
}
@@ -5744,9 +5742,7 @@ export class SessionService {
57445742
runStatus,
57455743
runState,
57465744
);
5747-
this.finalizeTerminalCloudTask(taskId, taskRunId, {
5748-
status: runStatus,
5749-
});
5745+
this.finalizeTerminalCloudTask(taskRunId, runStatus);
57505746
return () => {};
57515747
}
57525748

@@ -6172,14 +6168,10 @@ export class SessionService {
61726168
timestamp: Date.now(),
61736169
});
61746170
}
6175-
if (hasUserPrompt) {
6176-
// The real prompt has landed; the stash is no longer needed.
6177-
this.initialCloudOptimisticPrompt.delete(taskId);
6178-
this.d.store.clearTailOptimisticItems(taskRunId);
6179-
}
6180-
if (isTerminalRun) {
6181-
// A finished run gets no further echoes: any optimistic leftovers would
6182-
// otherwise linger as phantom tail items on the final transcript.
6171+
if (hasUserPrompt || isTerminalRun) {
6172+
// The stash is no longer needed once the real prompt lands - and a
6173+
// finished run gets no further echoes, so leftover optimistic items
6174+
// would otherwise linger as phantom tail items on the final transcript.
61836175
this.initialCloudOptimisticPrompt.delete(taskId);
61846176
this.d.store.clearTailOptimisticItems(taskRunId);
61856177
}
@@ -6197,26 +6189,17 @@ export class SessionService {
61976189
// Terminal hydration is different: it is the final transcript, so apply
61986190
// it whenever the persisted chain has more lines than the local stream.
61996191
const effectiveLineCount = Math.max(liveStreamLineCount, rawEntries.length);
6200-
if (isTerminalRun) {
6201-
if ((session.processedLineCount ?? 0) >= effectiveLineCount) {
6202-
this.surfacePersistedPendingPermissions(taskRunId, rawEntries);
6203-
this.pendingPermissionHydratedRuns.add(taskRunId);
6204-
return {
6205-
historyEntryCount: rawEntries.length,
6206-
liveStreamLineCount:
6207-
session.processedLineCount ?? liveStreamLineCount,
6208-
};
6209-
}
6210-
} else if (
6211-
session.processedLineCount !== undefined &&
6212-
session.processedLineCount > 0 &&
6213-
!isResumeRun
6214-
) {
6192+
const alreadyApplied = isTerminalRun
6193+
? (session.processedLineCount ?? 0) >= effectiveLineCount
6194+
: session.processedLineCount !== undefined &&
6195+
session.processedLineCount > 0 &&
6196+
!isResumeRun;
6197+
if (alreadyApplied) {
62156198
this.surfacePersistedPendingPermissions(taskRunId, rawEntries);
62166199
this.pendingPermissionHydratedRuns.add(taskRunId);
62176200
return {
62186201
historyEntryCount: rawEntries.length,
6219-
liveStreamLineCount: session.processedLineCount,
6202+
liveStreamLineCount: session.processedLineCount ?? liveStreamLineCount,
62206203
};
62216204
}
62226205

@@ -6247,7 +6230,7 @@ export class SessionService {
62476230
// path otherwise sees delta <= 0 and never re-evaluates the tail.
62486231
this.updatePromptStateFromEvents(taskRunId, events);
62496232
if (isTerminalRun) {
6250-
this.clearTerminalCloudPromptState(taskId, taskRunId);
6233+
this.clearTerminalCloudPromptState(taskRunId);
62516234
}
62526235
return {
62536236
historyEntryCount: rawEntries.length,
@@ -6256,28 +6239,18 @@ export class SessionService {
62566239
}
62576240

62586241
private finalizeTerminalCloudTask(
6259-
taskId: string,
62606242
taskRunId: string,
6261-
fields: {
6262-
status?: TaskRunStatus;
6263-
stage?: string | null;
6264-
output?: Record<string, unknown> | null;
6265-
errorMessage?: string | null;
6266-
branch?: string | null;
6267-
},
6243+
status: TaskRunStatus | undefined,
62686244
): void {
6269-
this.d.store.updateCloudStatus(taskRunId, fields);
6270-
this.clearTerminalCloudPromptState(taskId, taskRunId);
6245+
this.d.store.updateCloudStatus(taskRunId, { status });
6246+
this.clearTerminalCloudPromptState(taskRunId);
62716247
}
62726248

62736249
/**
62746250
* A terminal run can never flush its queue or answer a pending prompt;
62756251
* leaving those set keeps the composer in a busy state forever.
62766252
*/
6277-
private clearTerminalCloudPromptState(
6278-
taskId: string,
6279-
taskRunId: string,
6280-
): void {
6253+
private clearTerminalCloudPromptState(taskRunId: string): void {
62816254
const session = this.d.store.getSessions()[taskRunId];
62826255
if (
62836256
!session ||
@@ -6286,7 +6259,7 @@ export class SessionService {
62866259
return;
62876260
}
62886261

6289-
this.d.store.clearMessageQueue(taskId);
6262+
this.d.store.clearMessageQueue(session.taskId);
62906263
this.d.store.updateSession(taskRunId, {
62916264
isPromptPending: false,
62926265
});
@@ -7236,10 +7209,18 @@ export class SessionService {
72367209
}
72377210
}
72387211

7239-
if (update.kind === "snapshot" && !isTerminalStatus(update.status)) {
7240-
if (!this.isStaleNonTerminalCloudUpdate(taskRunId, update)) {
7241-
this.surfacePersistedPendingPermissions(taskRunId, update.newEntries);
7242-
}
7212+
// Evaluated once, before updateCloudStatus below can mutate cloudStatus.
7213+
const isStaleNonTerminalStatus = this.isStaleNonTerminalCloudUpdate(
7214+
taskRunId,
7215+
update,
7216+
);
7217+
7218+
if (
7219+
update.kind === "snapshot" &&
7220+
!isTerminalStatus(update.status) &&
7221+
!isStaleNonTerminalStatus
7222+
) {
7223+
this.surfacePersistedPendingPermissions(taskRunId, update.newEntries);
72437224
}
72447225

72457226
// NOTE: Don't auto-flush on `!isPromptPending && queue.length > 0` here.
@@ -7252,7 +7233,7 @@ export class SessionService {
72527233

72537234
// Update cloud status fields if present
72547235
if (update.kind === "status" || update.kind === "snapshot") {
7255-
if (!this.isStaleNonTerminalCloudUpdate(taskRunId, update)) {
7236+
if (!isStaleNonTerminalStatus) {
72567237
this.d.store.updateCloudStatus(taskRunId, {
72577238
status: update.status,
72587239
stage: update.stage,
@@ -7269,17 +7250,8 @@ export class SessionService {
72697250
}
72707251

72717252
if (isTerminalStatus(update.status)) {
7272-
// Clean up any pending resume messages that couldn't be sent
7273-
const session = this.d.store.getSessions()[taskRunId];
7274-
if (
7275-
session &&
7276-
(session.messageQueue.length > 0 || session.isPromptPending)
7277-
) {
7278-
this.d.store.clearMessageQueue(session.taskId);
7279-
this.d.store.updateSession(taskRunId, {
7280-
isPromptPending: false,
7281-
});
7282-
}
7253+
// Pending resume messages can never be sent to a settled run.
7254+
this.clearTerminalCloudPromptState(taskRunId);
72837255
this.stopCloudTaskWatch(update.taskId);
72847256
}
72857257
}

packages/core/src/sessions/sessionViewState.ts

Lines changed: 2 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import {
44
type Task,
55
type TaskRunStatus,
66
} from "@posthog/shared/domain-types";
7+
import { resolveEffectiveCloudStatus } from "../task-detail/cloudRunState";
78

89
export interface SessionViewState {
910
isCloudRunNotTerminal: boolean;
@@ -27,15 +28,7 @@ export function deriveSessionViewState(
2728
workspace: Workspace | null,
2829
isCloud: boolean,
2930
): SessionViewState {
30-
const taskRunId = task.latest_run?.id;
31-
const taskRunStatus = task.latest_run?.status ?? null;
32-
const sessionMatchesLatestRun =
33-
!!taskRunId && session?.taskRunId === taskRunId;
34-
const cloudStatus = sessionMatchesLatestRun
35-
? isTerminalStatus(taskRunStatus)
36-
? taskRunStatus
37-
: (session?.cloudStatus ?? taskRunStatus)
38-
: (taskRunStatus ?? session?.cloudStatus ?? null);
31+
const cloudStatus = resolveEffectiveCloudStatus(task, session);
3932
const isCloudRunTerminal = isCloud && isTerminalStatus(cloudStatus);
4033
const isCloudRunNotTerminal = isCloud && !isCloudRunTerminal;
4134

0 commit comments

Comments
 (0)