Skip to content

Commit 58d570b

Browse files
authored
feat: tps agent logs --follow race condition fix (ops-94) (#192)
* task complete: 39e2f6da-6003-43c2-ad43-3c246271d0a2 * boot * task complete: 909a43d4-69df-4c28-9eab-f2934be5813e
1 parent 12e660f commit 58d570b

4 files changed

Lines changed: 42 additions & 15 deletions

File tree

.tps-agent.pid

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
58542

packages/cli/bin/tps.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -309,7 +309,7 @@ async function main() {
309309
action: "logs",
310310
id: getFlag("id") ?? rest[1],
311311
lines: cli.flags.lines as number | undefined,
312-
follow: cli.flags.follow as boolean | undefined,
312+
follow: (cli.flags.follow as boolean | undefined) || process.argv.includes("-f"),
313313
});
314314
} else if (action === "commit") {
315315
// Inline helpers (getAuthor/getRepeatedFlags defined in else block below)

packages/cli/src/commands/agent.ts

Lines changed: 30 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -110,14 +110,22 @@ function tailLines(content: string, count: number): string[] {
110110

111111
async function streamLogUpdates(logPath: string, offset: number): Promise<never> {
112112
let position = offset;
113+
let reading = false;
114+
let pending = false;
113115

114116
const readFromPosition = (): void => {
117+
if (reading) {
118+
pending = true;
119+
return;
120+
}
121+
115122
const size = statSync(logPath).size;
116123
if (size < position) {
117124
position = 0;
118125
}
119126
if (size === position) return;
120127

128+
reading = true;
121129
const stream = createReadStream(logPath, {
122130
encoding: "utf-8",
123131
start: position,
@@ -130,17 +138,33 @@ async function streamLogUpdates(logPath: string, offset: number): Promise<never>
130138

131139
stream.on("end", () => {
132140
position = size;
141+
reading = false;
142+
if (pending) {
143+
pending = false;
144+
readFromPosition();
145+
}
133146
});
134-
};
135147

136-
readFromPosition();
148+
stream.on("error", () => {
149+
reading = false;
150+
});
151+
};
137152

138-
await new Promise<never>((_resolve) => {
139-
watch(logPath, { persistent: true }, (eventType) => {
153+
await new Promise<never>(() => {
154+
const watcher = watch(logPath, { persistent: true }, (eventType) => {
140155
if (eventType === "change" || eventType === "rename") {
141156
readFromPosition();
142157
}
143158
});
159+
160+
const handleSigint = (): void => {
161+
watcher.close();
162+
process.removeListener("SIGINT", handleSigint);
163+
process.stdout.write("\n");
164+
process.exit(0);
165+
};
166+
167+
process.on("SIGINT", handleSigint);
144168
});
145169

146170
process.exit(0);
@@ -149,12 +173,12 @@ async function streamLogUpdates(logPath: string, offset: number): Promise<never>
149173
async function logAgentRuntime(args: AgentArgs): Promise<void> {
150174
const agentId = args.id;
151175
if (!agentId) {
152-
console.error("Usage: tps agent logs --id <agent-id> [--lines <N>] [--follow]");
176+
console.error("Usage: tps agent logs --id <agent-id> [--lines <N>] [--follow|-f]");
153177
process.exit(1);
154178
}
155179

156180
const lineCount = normalizeLogLineCount(args.lines);
157-
const logPath = join(healthcheckHomeDir(), ".tps", "logs", `${agentId}.log`);
181+
const logPath = join(healthcheckHomeDir(), ".tps", "agents", agentId, "session.log");
158182
if (!existsSync(logPath)) {
159183
console.error(`No log file found for ${agentId}`);
160184
process.exit(1);

packages/cli/test/agent-runtime-logs.test.ts

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,12 @@ describe("tps agent logs runtime tail", () => {
1111
let originalError: typeof console.error;
1212
let originalExit: typeof process.exit;
1313

14+
function writeSessionLog(agentId: string, content: string): void {
15+
const logDir = join(tempHome, ".tps", "agents", agentId);
16+
mkdirSync(logDir, { recursive: true });
17+
writeFileSync(join(logDir, "session.log"), content, "utf-8");
18+
}
19+
1420
beforeEach(() => {
1521
tempHome = mkdtempSync(join(tmpdir(), "tps-agent-runtime-logs-"));
1622
originalHome = process.env.HOME;
@@ -35,11 +41,9 @@ describe("tps agent logs runtime tail", () => {
3541
test("shows the last 50 lines by default", async () => {
3642
const { runAgent } = await import("../src/commands/agent.js");
3743
const output: string[] = [];
38-
mkdirSync(join(tempHome, ".tps", "logs"), { recursive: true });
39-
writeFileSync(
40-
join(tempHome, ".tps", "logs", "ember.log"),
44+
writeSessionLog(
45+
"ember",
4146
Array.from({ length: 60 }, (_, index) => `line-${index + 1}`).join("\n") + "\n",
42-
"utf-8",
4347
);
4448
process.stdout.write = ((chunk: string | Uint8Array) => {
4549
output.push(typeof chunk === "string" ? chunk : Buffer.from(chunk).toString("utf-8"));
@@ -56,11 +60,9 @@ describe("tps agent logs runtime tail", () => {
5660
test("respects a custom line count", async () => {
5761
const { runAgent } = await import("../src/commands/agent.js");
5862
const output: string[] = [];
59-
mkdirSync(join(tempHome, ".tps", "logs"), { recursive: true });
60-
writeFileSync(
61-
join(tempHome, ".tps", "logs", "ember.log"),
63+
writeSessionLog(
64+
"ember",
6265
["alpha", "beta", "gamma", "delta"].join("\n"),
63-
"utf-8",
6466
);
6567
process.stdout.write = ((chunk: string | Uint8Array) => {
6668
output.push(typeof chunk === "string" ? chunk : Buffer.from(chunk).toString("utf-8"));

0 commit comments

Comments
 (0)