@agimon-ai/po-runner 0.0.0-alpha.1 → 0.0.0-alpha.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -3,6 +3,7 @@ const require_rolldown_runtime = require("../rolldown-runtime-VH7oDXx4.cjs");
3
3
  const require_ansiScrub = require("../ansiScrub-D5gpHDch.cjs");
4
4
  let node_fs = require("node:fs");
5
5
  node_fs = require_rolldown_runtime.__toESM(node_fs, 1);
6
+ let node_string_decoder = require("node:string_decoder");
6
7
  let node_url = require("node:url");
7
8
  let _agimon_ai_po_telemetry = require("@agimon-ai/po-telemetry");
8
9
  //#region src/bin/logSink.ts
@@ -35,16 +36,18 @@ async function main(argv = process.argv.slice(2)) {
35
36
  }
36
37
  let chunks = 0;
37
38
  const rawCeiling = Number.parseInt(maxBytesRaw, 10);
38
- const raw = rawPath === void 0 || rawPath === "" ? void 0 : openRaw(rawPath);
39
- let rawWritten = raw === void 0 ? 0 : rawSize(rawPath);
39
+ let raw = rawPath === void 0 || rawPath === "" ? void 0 : openRaw(rawPath);
40
+ let rawWritten = 0;
41
+ const decoder = new node_string_decoder.StringDecoder("utf8");
40
42
  try {
41
- process.stdin.setEncoding("utf8");
42
43
  for await (const chunk of process.stdin) {
43
- const source = String(chunk);
44
+ const rawBytes = Buffer.isBuffer(chunk) ? chunk : Buffer.from(String(chunk));
45
+ const source = decoder.write(rawBytes);
44
46
  if (raw !== void 0) {
45
- const rawBytes = Buffer.from(source, "utf8");
46
47
  if (rawWritten + rawBytes.byteLength > rawCeiling) {
47
- node_fs.default.ftruncateSync(raw, 0);
48
+ const previous = raw;
49
+ raw = openRaw(rawPath);
50
+ node_fs.default.closeSync(previous);
48
51
  rawWritten = 0;
49
52
  }
50
53
  node_fs.default.writeSync(raw, rawBytes);
@@ -57,6 +60,12 @@ async function main(argv = process.argv.slice(2)) {
57
60
  written += bytes.byteLength;
58
61
  chunks += 1;
59
62
  }
63
+ const remaining = require_ansiScrub.scrubTerminalOutput(decoder.end());
64
+ if (remaining) {
65
+ const bytes = Buffer.from(remaining);
66
+ node_fs.default.writeSync(handle, bytes);
67
+ written += bytes.byteLength;
68
+ }
60
69
  node_fs.default.closeSync(handle);
61
70
  if (raw !== void 0) node_fs.default.closeSync(raw);
62
71
  writeDone(donePath);
@@ -74,15 +83,17 @@ async function main(argv = process.argv.slice(2)) {
74
83
  await telemetry.shutdown();
75
84
  }
76
85
  }
77
- /** Truncating rather than appending: a stale pane from a previous run is not this one's. */
86
+ /** Publish a fresh inode so readers can identify rollover from the descriptor they read. */
78
87
  function openRaw(target) {
79
- return node_fs.default.openSync(target, "w");
80
- }
81
- function rawSize(target) {
88
+ const temporary = `${target}.${process.pid}.tmp`;
89
+ const descriptor = node_fs.default.openSync(temporary, "wx", 384);
82
90
  try {
83
- return node_fs.default.statSync(target).size;
84
- } catch {
85
- return 0;
91
+ node_fs.default.renameSync(temporary, target);
92
+ return descriptor;
93
+ } catch (error) {
94
+ node_fs.default.closeSync(descriptor);
95
+ node_fs.default.unlinkSync(temporary);
96
+ throw error;
86
97
  }
87
98
  }
88
99
  function writeDone(target) {
@@ -1 +1 @@
1
- {"version":3,"file":"logSink.cjs","names":["createDoomTelemetry","fs","scrubTerminalOutput","pathToFileURL"],"sources":["../../src/bin/logSink.ts"],"sourcesContent":["import fs from 'node:fs';\nimport { pathToFileURL } from 'node:url';\n\nimport { createDoomTelemetry } from '@agimon-ai/po-telemetry';\n\nimport { scrubTerminalOutput } from '../services/ansiScrub';\n\nexport async function main(argv: readonly string[] = process.argv.slice(2)): Promise<number> {\n const startedAt = Date.now();\n const telemetry = createDoomTelemetry({\n serviceName: 'doom-runner-log-sink',\n packageName: '@agimon-ai/po-runner',\n env: process.env,\n enableLogs: true,\n enableTraces: true,\n });\n const [logPath, rotatedPath, maxBytesRaw, donePath, rawPath] = argv;\n try {\n // Keep the legacy argument shape for the published executable subpath. The\n // scrubbed log is append-only, so the former rotation path is unused; the\n // ceiling now bounds the raw copy, which only interactive runs ask for.\n if (!logPath || !rotatedPath || !maxBytesRaw || !donePath) {\n throw new Error('logSink requires log, rotated log, max bytes, and completion paths');\n }\n } catch (error) {\n await telemetry.recordError('doom_runner.file_sink_failed', error, { duration_ms: Date.now() - startedAt });\n await telemetry.shutdown();\n throw error;\n }\n\n let handle: number;\n let written: number;\n try {\n handle = fs.openSync(logPath, 'a');\n written = fs.statSync(logPath).size;\n } catch (error) {\n await telemetry.recordError('doom_runner.file_sink_failed', error, { duration_ms: Date.now() - startedAt });\n await telemetry.shutdown();\n throw error;\n }\n let chunks = 0;\n // The pane's bytes before anything is taken out of them. Scrubbing exists so\n // the log stays worth grepping, and it is exactly what makes the log useless\n // to a terminal, so an attached view needs its own faithful copy.\n const rawCeiling = Number.parseInt(maxBytesRaw, 10);\n const raw = rawPath === undefined || rawPath === '' ? undefined : openRaw(rawPath);\n let rawWritten = raw === undefined ? 0 : rawSize(rawPath as string);\n try {\n process.stdin.setEncoding('utf8');\n for await (const chunk of process.stdin) {\n const source = String(chunk);\n if (raw !== undefined) {\n const rawBytes = Buffer.from(source, 'utf8');\n // A terminal that scrolls all day must not fill the disk. Past the\n // ceiling the copy starts over: a reader attaching later wants the\n // recent screen, and no part of this is the record of the run.\n if (rawWritten + rawBytes.byteLength > rawCeiling) {\n fs.ftruncateSync(raw, 0);\n rawWritten = 0;\n }\n fs.writeSync(raw, rawBytes);\n rawWritten += rawBytes.byteLength;\n }\n const text = scrubTerminalOutput(source);\n if (!text) continue;\n const bytes = Buffer.from(text, 'utf8');\n fs.writeSync(handle, bytes);\n written += bytes.byteLength;\n chunks += 1;\n }\n fs.closeSync(handle);\n if (raw !== undefined) fs.closeSync(raw);\n writeDone(donePath);\n await telemetry.recordEvent('doom_runner.file_sink_finished', {\n outcome: 'completed',\n 'runner.chunk_count': chunks,\n 'runner.bytes_written': written,\n duration_ms: Date.now() - startedAt,\n });\n return 0;\n } catch (error) {\n await telemetry.recordError('doom_runner.file_sink_failed', error, { duration_ms: Date.now() - startedAt });\n throw error;\n } finally {\n await telemetry.shutdown();\n }\n}\n\n/** Truncating rather than appending: a stale pane from a previous run is not this one's. */\nfunction openRaw(target: string): number {\n return fs.openSync(target, 'w');\n}\n\nfunction rawSize(target: string): number {\n try {\n return fs.statSync(target).size;\n } catch {\n return 0;\n }\n}\n\nfunction writeDone(target: string): void {\n const temporary = `${target}.${process.pid}.tmp`;\n fs.writeFileSync(temporary, '', { mode: 0o600 });\n fs.renameSync(temporary, target);\n}\n\nif (process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href) {\n main().then(\n (code) => {\n process.exitCode = code;\n },\n (error: unknown) => {\n process.stderr.write(`${error instanceof Error ? error.message : String(error)}\\n`);\n process.exitCode = 1;\n },\n );\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"logSink.cjs","names":["createDoomTelemetry","fs","StringDecoder","scrubTerminalOutput","pathToFileURL"],"sources":["../../src/bin/logSink.ts"],"sourcesContent":["import fs from 'node:fs';\nimport { StringDecoder } from 'node:string_decoder';\nimport { pathToFileURL } from 'node:url';\n\nimport { createDoomTelemetry } from '@agimon-ai/po-telemetry';\n\nimport { scrubTerminalOutput } from '../services/ansiScrub';\n\nexport async function main(argv: readonly string[] = process.argv.slice(2)): Promise<number> {\n const startedAt = Date.now();\n const telemetry = createDoomTelemetry({\n serviceName: 'doom-runner-log-sink',\n packageName: '@agimon-ai/po-runner',\n env: process.env,\n enableLogs: true,\n enableTraces: true,\n });\n const [logPath, rotatedPath, maxBytesRaw, donePath, rawPath] = argv;\n try {\n // Keep the legacy argument shape for the published executable subpath. The\n // scrubbed log is append-only, so the former rotation path is unused; the\n // ceiling now bounds the raw copy, which only interactive runs ask for.\n if (!logPath || !rotatedPath || !maxBytesRaw || !donePath) {\n throw new Error('logSink requires log, rotated log, max bytes, and completion paths');\n }\n } catch (error) {\n await telemetry.recordError('doom_runner.file_sink_failed', error, { duration_ms: Date.now() - startedAt });\n await telemetry.shutdown();\n throw error;\n }\n\n let handle: number;\n let written: number;\n try {\n handle = fs.openSync(logPath, 'a');\n written = fs.statSync(logPath).size;\n } catch (error) {\n await telemetry.recordError('doom_runner.file_sink_failed', error, { duration_ms: Date.now() - startedAt });\n await telemetry.shutdown();\n throw error;\n }\n let chunks = 0;\n // The pane's bytes before anything is taken out of them. Scrubbing exists so\n // the log stays worth grepping, and it is exactly what makes the log useless\n // to a terminal, so an attached view needs its own faithful copy.\n const rawCeiling = Number.parseInt(maxBytesRaw, 10);\n let raw = rawPath === undefined || rawPath === '' ? undefined : openRaw(rawPath);\n let rawWritten = 0;\n const decoder = new StringDecoder('utf8');\n try {\n for await (const chunk of process.stdin) {\n const rawBytes = Buffer.isBuffer(chunk) ? chunk : Buffer.from(String(chunk));\n const source = decoder.write(rawBytes);\n if (raw !== undefined) {\n // A terminal that scrolls all day must not fill the disk. Past the\n // ceiling the copy starts over: a reader attaching later wants the\n // recent screen, and no part of this is the record of the run.\n if (rawWritten + rawBytes.byteLength > rawCeiling) {\n const previous = raw;\n raw = openRaw(rawPath as string);\n fs.closeSync(previous);\n rawWritten = 0;\n }\n fs.writeSync(raw, rawBytes);\n rawWritten += rawBytes.byteLength;\n }\n const text = scrubTerminalOutput(source);\n if (!text) continue;\n const bytes = Buffer.from(text, 'utf8');\n fs.writeSync(handle, bytes);\n written += bytes.byteLength;\n chunks += 1;\n }\n const remaining = scrubTerminalOutput(decoder.end());\n if (remaining) {\n const bytes = Buffer.from(remaining);\n fs.writeSync(handle, bytes);\n written += bytes.byteLength;\n }\n fs.closeSync(handle);\n if (raw !== undefined) fs.closeSync(raw);\n writeDone(donePath);\n await telemetry.recordEvent('doom_runner.file_sink_finished', {\n outcome: 'completed',\n 'runner.chunk_count': chunks,\n 'runner.bytes_written': written,\n duration_ms: Date.now() - startedAt,\n });\n return 0;\n } catch (error) {\n await telemetry.recordError('doom_runner.file_sink_failed', error, { duration_ms: Date.now() - startedAt });\n throw error;\n } finally {\n await telemetry.shutdown();\n }\n}\n\n/** Publish a fresh inode so readers can identify rollover from the descriptor they read. */\nfunction openRaw(target: string): number {\n const temporary = `${target}.${process.pid}.tmp`;\n const descriptor = fs.openSync(temporary, 'wx', 0o600);\n try {\n fs.renameSync(temporary, target);\n return descriptor;\n } catch (error) {\n fs.closeSync(descriptor);\n fs.unlinkSync(temporary);\n throw error;\n }\n}\n\nfunction writeDone(target: string): void {\n const temporary = `${target}.${process.pid}.tmp`;\n fs.writeFileSync(temporary, '', { mode: 0o600 });\n fs.renameSync(temporary, target);\n}\n\nif (process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href) {\n main().then(\n (code) => {\n process.exitCode = code;\n },\n (error: unknown) => {\n process.stderr.write(`${error instanceof Error ? error.message : String(error)}\\n`);\n process.exitCode = 1;\n },\n );\n}\n"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"logSink.d.mts","names":[],"sources":["../../src/bin/logSink.ts"],"mappings":";wBAOsB,KAAK,2BAAkD"}
1
+ {"version":3,"file":"logSink.d.mts","names":[],"sources":["../../src/bin/logSink.ts"],"mappings":";wBAQsB,KAAK,2BAAkD"}
@@ -1,5 +1,6 @@
1
1
  import { n as scrubTerminalOutput } from "../ansiScrub-CsqTSaVT.mjs";
2
2
  import fs from "node:fs";
3
+ import { StringDecoder } from "node:string_decoder";
3
4
  import { pathToFileURL } from "node:url";
4
5
  import { createDoomTelemetry } from "@agimon-ai/po-telemetry";
5
6
  //#region src/bin/logSink.ts
@@ -32,16 +33,18 @@ async function main(argv = process.argv.slice(2)) {
32
33
  }
33
34
  let chunks = 0;
34
35
  const rawCeiling = Number.parseInt(maxBytesRaw, 10);
35
- const raw = rawPath === void 0 || rawPath === "" ? void 0 : openRaw(rawPath);
36
- let rawWritten = raw === void 0 ? 0 : rawSize(rawPath);
36
+ let raw = rawPath === void 0 || rawPath === "" ? void 0 : openRaw(rawPath);
37
+ let rawWritten = 0;
38
+ const decoder = new StringDecoder("utf8");
37
39
  try {
38
- process.stdin.setEncoding("utf8");
39
40
  for await (const chunk of process.stdin) {
40
- const source = String(chunk);
41
+ const rawBytes = Buffer.isBuffer(chunk) ? chunk : Buffer.from(String(chunk));
42
+ const source = decoder.write(rawBytes);
41
43
  if (raw !== void 0) {
42
- const rawBytes = Buffer.from(source, "utf8");
43
44
  if (rawWritten + rawBytes.byteLength > rawCeiling) {
44
- fs.ftruncateSync(raw, 0);
45
+ const previous = raw;
46
+ raw = openRaw(rawPath);
47
+ fs.closeSync(previous);
45
48
  rawWritten = 0;
46
49
  }
47
50
  fs.writeSync(raw, rawBytes);
@@ -54,6 +57,12 @@ async function main(argv = process.argv.slice(2)) {
54
57
  written += bytes.byteLength;
55
58
  chunks += 1;
56
59
  }
60
+ const remaining = scrubTerminalOutput(decoder.end());
61
+ if (remaining) {
62
+ const bytes = Buffer.from(remaining);
63
+ fs.writeSync(handle, bytes);
64
+ written += bytes.byteLength;
65
+ }
57
66
  fs.closeSync(handle);
58
67
  if (raw !== void 0) fs.closeSync(raw);
59
68
  writeDone(donePath);
@@ -71,15 +80,17 @@ async function main(argv = process.argv.slice(2)) {
71
80
  await telemetry.shutdown();
72
81
  }
73
82
  }
74
- /** Truncating rather than appending: a stale pane from a previous run is not this one's. */
83
+ /** Publish a fresh inode so readers can identify rollover from the descriptor they read. */
75
84
  function openRaw(target) {
76
- return fs.openSync(target, "w");
77
- }
78
- function rawSize(target) {
85
+ const temporary = `${target}.${process.pid}.tmp`;
86
+ const descriptor = fs.openSync(temporary, "wx", 384);
79
87
  try {
80
- return fs.statSync(target).size;
81
- } catch {
82
- return 0;
88
+ fs.renameSync(temporary, target);
89
+ return descriptor;
90
+ } catch (error) {
91
+ fs.closeSync(descriptor);
92
+ fs.unlinkSync(temporary);
93
+ throw error;
83
94
  }
84
95
  }
85
96
  function writeDone(target) {
@@ -1 +1 @@
1
- {"version":3,"file":"logSink.mjs","names":[],"sources":["../../src/bin/logSink.ts"],"sourcesContent":["import fs from 'node:fs';\nimport { pathToFileURL } from 'node:url';\n\nimport { createDoomTelemetry } from '@agimon-ai/po-telemetry';\n\nimport { scrubTerminalOutput } from '../services/ansiScrub';\n\nexport async function main(argv: readonly string[] = process.argv.slice(2)): Promise<number> {\n const startedAt = Date.now();\n const telemetry = createDoomTelemetry({\n serviceName: 'doom-runner-log-sink',\n packageName: '@agimon-ai/po-runner',\n env: process.env,\n enableLogs: true,\n enableTraces: true,\n });\n const [logPath, rotatedPath, maxBytesRaw, donePath, rawPath] = argv;\n try {\n // Keep the legacy argument shape for the published executable subpath. The\n // scrubbed log is append-only, so the former rotation path is unused; the\n // ceiling now bounds the raw copy, which only interactive runs ask for.\n if (!logPath || !rotatedPath || !maxBytesRaw || !donePath) {\n throw new Error('logSink requires log, rotated log, max bytes, and completion paths');\n }\n } catch (error) {\n await telemetry.recordError('doom_runner.file_sink_failed', error, { duration_ms: Date.now() - startedAt });\n await telemetry.shutdown();\n throw error;\n }\n\n let handle: number;\n let written: number;\n try {\n handle = fs.openSync(logPath, 'a');\n written = fs.statSync(logPath).size;\n } catch (error) {\n await telemetry.recordError('doom_runner.file_sink_failed', error, { duration_ms: Date.now() - startedAt });\n await telemetry.shutdown();\n throw error;\n }\n let chunks = 0;\n // The pane's bytes before anything is taken out of them. Scrubbing exists so\n // the log stays worth grepping, and it is exactly what makes the log useless\n // to a terminal, so an attached view needs its own faithful copy.\n const rawCeiling = Number.parseInt(maxBytesRaw, 10);\n const raw = rawPath === undefined || rawPath === '' ? undefined : openRaw(rawPath);\n let rawWritten = raw === undefined ? 0 : rawSize(rawPath as string);\n try {\n process.stdin.setEncoding('utf8');\n for await (const chunk of process.stdin) {\n const source = String(chunk);\n if (raw !== undefined) {\n const rawBytes = Buffer.from(source, 'utf8');\n // A terminal that scrolls all day must not fill the disk. Past the\n // ceiling the copy starts over: a reader attaching later wants the\n // recent screen, and no part of this is the record of the run.\n if (rawWritten + rawBytes.byteLength > rawCeiling) {\n fs.ftruncateSync(raw, 0);\n rawWritten = 0;\n }\n fs.writeSync(raw, rawBytes);\n rawWritten += rawBytes.byteLength;\n }\n const text = scrubTerminalOutput(source);\n if (!text) continue;\n const bytes = Buffer.from(text, 'utf8');\n fs.writeSync(handle, bytes);\n written += bytes.byteLength;\n chunks += 1;\n }\n fs.closeSync(handle);\n if (raw !== undefined) fs.closeSync(raw);\n writeDone(donePath);\n await telemetry.recordEvent('doom_runner.file_sink_finished', {\n outcome: 'completed',\n 'runner.chunk_count': chunks,\n 'runner.bytes_written': written,\n duration_ms: Date.now() - startedAt,\n });\n return 0;\n } catch (error) {\n await telemetry.recordError('doom_runner.file_sink_failed', error, { duration_ms: Date.now() - startedAt });\n throw error;\n } finally {\n await telemetry.shutdown();\n }\n}\n\n/** Truncating rather than appending: a stale pane from a previous run is not this one's. */\nfunction openRaw(target: string): number {\n return fs.openSync(target, 'w');\n}\n\nfunction rawSize(target: string): number {\n try {\n return fs.statSync(target).size;\n } catch {\n return 0;\n }\n}\n\nfunction writeDone(target: string): void {\n const temporary = `${target}.${process.pid}.tmp`;\n fs.writeFileSync(temporary, '', { mode: 0o600 });\n fs.renameSync(temporary, target);\n}\n\nif (process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href) {\n main().then(\n (code) => {\n process.exitCode = code;\n },\n (error: unknown) => {\n process.stderr.write(`${error instanceof Error ? error.message : String(error)}\\n`);\n process.exitCode = 1;\n },\n );\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"logSink.mjs","names":[],"sources":["../../src/bin/logSink.ts"],"sourcesContent":["import fs from 'node:fs';\nimport { StringDecoder } from 'node:string_decoder';\nimport { pathToFileURL } from 'node:url';\n\nimport { createDoomTelemetry } from '@agimon-ai/po-telemetry';\n\nimport { scrubTerminalOutput } from '../services/ansiScrub';\n\nexport async function main(argv: readonly string[] = process.argv.slice(2)): Promise<number> {\n const startedAt = Date.now();\n const telemetry = createDoomTelemetry({\n serviceName: 'doom-runner-log-sink',\n packageName: '@agimon-ai/po-runner',\n env: process.env,\n enableLogs: true,\n enableTraces: true,\n });\n const [logPath, rotatedPath, maxBytesRaw, donePath, rawPath] = argv;\n try {\n // Keep the legacy argument shape for the published executable subpath. The\n // scrubbed log is append-only, so the former rotation path is unused; the\n // ceiling now bounds the raw copy, which only interactive runs ask for.\n if (!logPath || !rotatedPath || !maxBytesRaw || !donePath) {\n throw new Error('logSink requires log, rotated log, max bytes, and completion paths');\n }\n } catch (error) {\n await telemetry.recordError('doom_runner.file_sink_failed', error, { duration_ms: Date.now() - startedAt });\n await telemetry.shutdown();\n throw error;\n }\n\n let handle: number;\n let written: number;\n try {\n handle = fs.openSync(logPath, 'a');\n written = fs.statSync(logPath).size;\n } catch (error) {\n await telemetry.recordError('doom_runner.file_sink_failed', error, { duration_ms: Date.now() - startedAt });\n await telemetry.shutdown();\n throw error;\n }\n let chunks = 0;\n // The pane's bytes before anything is taken out of them. Scrubbing exists so\n // the log stays worth grepping, and it is exactly what makes the log useless\n // to a terminal, so an attached view needs its own faithful copy.\n const rawCeiling = Number.parseInt(maxBytesRaw, 10);\n let raw = rawPath === undefined || rawPath === '' ? undefined : openRaw(rawPath);\n let rawWritten = 0;\n const decoder = new StringDecoder('utf8');\n try {\n for await (const chunk of process.stdin) {\n const rawBytes = Buffer.isBuffer(chunk) ? chunk : Buffer.from(String(chunk));\n const source = decoder.write(rawBytes);\n if (raw !== undefined) {\n // A terminal that scrolls all day must not fill the disk. Past the\n // ceiling the copy starts over: a reader attaching later wants the\n // recent screen, and no part of this is the record of the run.\n if (rawWritten + rawBytes.byteLength > rawCeiling) {\n const previous = raw;\n raw = openRaw(rawPath as string);\n fs.closeSync(previous);\n rawWritten = 0;\n }\n fs.writeSync(raw, rawBytes);\n rawWritten += rawBytes.byteLength;\n }\n const text = scrubTerminalOutput(source);\n if (!text) continue;\n const bytes = Buffer.from(text, 'utf8');\n fs.writeSync(handle, bytes);\n written += bytes.byteLength;\n chunks += 1;\n }\n const remaining = scrubTerminalOutput(decoder.end());\n if (remaining) {\n const bytes = Buffer.from(remaining);\n fs.writeSync(handle, bytes);\n written += bytes.byteLength;\n }\n fs.closeSync(handle);\n if (raw !== undefined) fs.closeSync(raw);\n writeDone(donePath);\n await telemetry.recordEvent('doom_runner.file_sink_finished', {\n outcome: 'completed',\n 'runner.chunk_count': chunks,\n 'runner.bytes_written': written,\n duration_ms: Date.now() - startedAt,\n });\n return 0;\n } catch (error) {\n await telemetry.recordError('doom_runner.file_sink_failed', error, { duration_ms: Date.now() - startedAt });\n throw error;\n } finally {\n await telemetry.shutdown();\n }\n}\n\n/** Publish a fresh inode so readers can identify rollover from the descriptor they read. */\nfunction openRaw(target: string): number {\n const temporary = `${target}.${process.pid}.tmp`;\n const descriptor = fs.openSync(temporary, 'wx', 0o600);\n try {\n fs.renameSync(temporary, target);\n return descriptor;\n } catch (error) {\n fs.closeSync(descriptor);\n fs.unlinkSync(temporary);\n throw error;\n }\n}\n\nfunction writeDone(target: string): void {\n const temporary = `${target}.${process.pid}.tmp`;\n fs.writeFileSync(temporary, '', { mode: 0o600 });\n fs.renameSync(temporary, target);\n}\n\nif (process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href) {\n main().then(\n (code) => {\n process.exitCode = code;\n },\n (error: unknown) => {\n process.stderr.write(`${error instanceof Error ? error.message : String(error)}\\n`);\n process.exitCode = 1;\n },\n );\n}\n"],"mappings":"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"}