




















@@ -1,3 +1,9 @@
1+/**
2+ * Session trajectory tail command.
3+ *
4+ * It selects active or requested sessions, renders recent trajectory events,
5+ * and can follow append-only trajectory files across rotation/truncation.
6+ */
17import fs from "node:fs";
28import path from "node:path";
39import { readAcpSessionMeta } from "../acp/runtime/session-meta.js";
@@ -64,6 +70,7 @@ const EVENT_TYPE_PAD = 16;
6470const FOLLOW_INTERVAL_MS = 1_000;
6571let followIntervalMsForTests: number | undefined;
667273+/** Overrides the follow polling interval for tests. */
6774export function setSessionsTailFollowIntervalMsForTests(intervalMs?: number): void {
6875followIntervalMsForTests = intervalMs;
6976}
@@ -146,6 +153,8 @@ function compareCursors(left: TrajectoryCursor, right: TrajectoryCursor): number
146153if (left.seq !== null && right.seq !== null && left.seq !== right.seq) {
147154return left.seq - right.seq;
148155}
156+// Some trajectory events lack sequence numbers; timestamp fallback keeps
157+// follow mode from replaying already-rendered events after file rewrites.
149158const byTimestamp = left.tsMs - right.tsMs;
150159if (byTimestamp !== 0) {
151160return byTimestamp;
@@ -241,6 +250,8 @@ function safePreview(event: TrajectoryEvent): string {
241250return `prompt skipped${reason ? `: ${reason}` : ""}`;
242251}
243252case "tool.call":
253+// Tool arguments may contain secrets or user text; tail output shows only
254+// the tool name and a redacted placeholder.
244255return `${toolName(data)} {...redacted...}`;
245256case "tool.timeout":
246257return `${toolName(data)} timeout`;
@@ -372,6 +383,8 @@ function selectSessionsToTail(selections: TailSelection[], sessionKey?: string):
372383373384const running = selections.filter((selection) => isRunningSession(selection));
374385if (running.length > 0) {
386+// Without an explicit key, prefer all running sessions so follow mode shows
387+// concurrent active work instead of only the newest store entry.
375388return running.toSorted(compareSelectionsByUpdatedAt);
376389}
377390@@ -405,6 +418,8 @@ function readNewFollowEvents(state: FollowState): TrajectoryEvent[] {
405418fileState.size === state.offset && state.fileState?.mtimeMs !== fileState.mtimeMs;
406419407420if (replaced || truncated || possiblyRewrittenSameSize) {
421+// Log rotation, truncation, and same-size rewrites all require a full
422+// rescan; cursor filtering prevents duplicate event output.
408423const snapshot = readTrajectorySnapshot(state.selection.trajectoryPath);
409424state.fileState = snapshot.fileState;
410425state.offset = snapshot.offset;
@@ -424,6 +439,8 @@ function readNewFollowEvents(state: FollowState): TrajectoryEvent[] {
424439state.offset = fileState.size;
425440state.fileState = fileState;
426441const combined = `${state.pending}${buffer.toString("utf8")}`;
442+// Keep an incomplete trailing JSON line until the next poll, matching
443+// append-only writers that flush in chunks.
427444const lines = combined.split(/\r?\n/u);
428445state.pending = lines.pop() ?? "";
429446return parseTrajectoryEventLines(lines);
@@ -492,6 +509,7 @@ function resolveTailTargetAgent(opts: SessionsTailOptions): string | undefined {
492509return opts.sessionKey?.trim() ? resolveAgentIdFromSessionKey(opts.sessionKey) : undefined;
493510}
494511512+/** Tails recent trajectory events for the selected session(s). */
495513export async function sessionsTailCommand(
496514opts: SessionsTailOptions,
497515runtime: RuntimeEnv,
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。