tines 0.0.165 → 0.0.167
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.
- package/dist/index.js +206 -25
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -3130,9 +3130,9 @@ Expecting one of '${allowedValues.join("', '")}'`);
|
|
|
3130
3130
|
helpWidth: context.helpWidth,
|
|
3131
3131
|
outputHasColors: context.hasColors
|
|
3132
3132
|
});
|
|
3133
|
-
const
|
|
3134
|
-
if (context.hasColors) return
|
|
3135
|
-
return this._outputConfiguration.stripColor(
|
|
3133
|
+
const text3 = helper.formatHelp(this, helper);
|
|
3134
|
+
if (context.hasColors) return text3;
|
|
3135
|
+
return this._outputConfiguration.stripColor(text3);
|
|
3136
3136
|
}
|
|
3137
3137
|
/**
|
|
3138
3138
|
* @typedef HelpContext
|
|
@@ -3292,7 +3292,7 @@ Expecting one of '${allowedValues.join("', '")}'`);
|
|
|
3292
3292
|
* @param {(string | Function)} text - string to add, or a function returning a string
|
|
3293
3293
|
* @return {Command} `this` command for chaining
|
|
3294
3294
|
*/
|
|
3295
|
-
addHelpText(position,
|
|
3295
|
+
addHelpText(position, text3) {
|
|
3296
3296
|
const allowedValues = ["beforeAll", "before", "after", "afterAll"];
|
|
3297
3297
|
if (!allowedValues.includes(position)) {
|
|
3298
3298
|
throw new Error(`Unexpected value for position to addHelpText.
|
|
@@ -3301,10 +3301,10 @@ Expecting one of '${allowedValues.join("', '")}'`);
|
|
|
3301
3301
|
const helpEvent = `${position}Help`;
|
|
3302
3302
|
this.on(helpEvent, (context) => {
|
|
3303
3303
|
let helpStr;
|
|
3304
|
-
if (typeof
|
|
3305
|
-
helpStr =
|
|
3304
|
+
if (typeof text3 === "function") {
|
|
3305
|
+
helpStr = text3({ error: context.error, command: context.command });
|
|
3306
3306
|
} else {
|
|
3307
|
-
helpStr =
|
|
3307
|
+
helpStr = text3;
|
|
3308
3308
|
}
|
|
3309
3309
|
if (helpStr) {
|
|
3310
3310
|
context.write(`${helpStr}
|
|
@@ -3527,7 +3527,7 @@ function runCostLabel(run) {
|
|
|
3527
3527
|
if (!usage) return null;
|
|
3528
3528
|
if (usage.cost_usd !== void 0) return `$${usage.cost_usd.toFixed(2)}`;
|
|
3529
3529
|
if (usage.cost_source === "none") return "unreported";
|
|
3530
|
-
const tokens = (usage.input_tokens ?? 0) + (usage.output_tokens ?? 0);
|
|
3530
|
+
const tokens = (usage.input_tokens ?? 0) + (usage.output_tokens ?? 0) + (usage.cache_read_tokens ?? 0) + (usage.cache_write_tokens ?? 0);
|
|
3531
3531
|
return tokens > 0 ? `${tokens.toLocaleString()} tok` : null;
|
|
3532
3532
|
}
|
|
3533
3533
|
function utilizationLabel(quota, activeRuns, stateName = (id) => id) {
|
|
@@ -6366,12 +6366,12 @@ start one: tines journal append ${issue.project_name}/${issue.number} "- <date>:
|
|
|
6366
6366
|
).action(
|
|
6367
6367
|
async (ref, markdown, opts, command) => {
|
|
6368
6368
|
if (helpGuard(command, markdown)) return;
|
|
6369
|
-
const
|
|
6369
|
+
const text3 = readBodyValue(markdown);
|
|
6370
6370
|
const api = client(opts);
|
|
6371
6371
|
const { scope, note, item } = await resolveJournal(api, ref, opts.state);
|
|
6372
6372
|
printNote(note);
|
|
6373
6373
|
if (item) {
|
|
6374
|
-
const updated = await api.appendContextItem(item.id, { text:
|
|
6374
|
+
const updated = await api.appendContextItem(item.id, { text: text3 });
|
|
6375
6375
|
if (opts.json) return printJson(updated);
|
|
6376
6376
|
return console.log(`appended to the ${scope.label} journal (now v${updated.version})`);
|
|
6377
6377
|
}
|
|
@@ -6381,7 +6381,7 @@ start one: tines journal append ${issue.project_name}/${issue.number} "- <date>:
|
|
|
6381
6381
|
name: JOURNAL_NAME,
|
|
6382
6382
|
project_id: scope.project_id ?? void 0,
|
|
6383
6383
|
workflow_state_id: scope.workflow_state_id ?? void 0,
|
|
6384
|
-
body:
|
|
6384
|
+
body: text3.trim()
|
|
6385
6385
|
});
|
|
6386
6386
|
if (opts.json) return printJson(created);
|
|
6387
6387
|
console.log(`started the ${scope.label} journal (${created.id})`);
|
|
@@ -6389,7 +6389,7 @@ start one: tines journal append ${issue.project_name}/${issue.number} "- <date>:
|
|
|
6389
6389
|
if (!(err instanceof ApiError) || err.code !== "duplicate_context_name") throw err;
|
|
6390
6390
|
const { item: fresh } = await resolveJournal(api, ref, opts.state);
|
|
6391
6391
|
if (!fresh) throw err;
|
|
6392
|
-
const updated = await api.appendContextItem(fresh.id, { text:
|
|
6392
|
+
const updated = await api.appendContextItem(fresh.id, { text: text3 });
|
|
6393
6393
|
if (opts.json) return printJson(updated);
|
|
6394
6394
|
console.log(`appended to the ${scope.label} journal (now v${updated.version})`);
|
|
6395
6395
|
}
|
|
@@ -6938,6 +6938,7 @@ import {
|
|
|
6938
6938
|
} from "node:fs";
|
|
6939
6939
|
import { hostname, platform, arch } from "node:os";
|
|
6940
6940
|
import { dirname as dirname4, join as join5 } from "node:path";
|
|
6941
|
+
import { StringDecoder } from "node:string_decoder";
|
|
6941
6942
|
|
|
6942
6943
|
// src/version.ts
|
|
6943
6944
|
import { readFileSync as readFileSync6 } from "node:fs";
|
|
@@ -6950,6 +6951,17 @@ function cliVersion() {
|
|
|
6950
6951
|
}
|
|
6951
6952
|
}
|
|
6952
6953
|
|
|
6954
|
+
// src/daemon/stream-summary.ts
|
|
6955
|
+
function validMetric(value) {
|
|
6956
|
+
return typeof value === "number" && Number.isFinite(value) && value >= 0;
|
|
6957
|
+
}
|
|
6958
|
+
function validProviderSessionId(value) {
|
|
6959
|
+
return typeof value === "string" && value.length >= 1 && value.length <= 255 && Boolean(value.trim()) && !/[\x00-\x1f\x7f]/.test(value);
|
|
6960
|
+
}
|
|
6961
|
+
function copySummary(value) {
|
|
6962
|
+
return { ...value, ...value.usage ? { usage: { ...value.usage } } : {} };
|
|
6963
|
+
}
|
|
6964
|
+
|
|
6953
6965
|
// src/daemon/claude-stream.ts
|
|
6954
6966
|
function clip(value, max) {
|
|
6955
6967
|
return value.length > max ? `${value.slice(0, max)}\u2026` : value;
|
|
@@ -7021,6 +7033,8 @@ var ClaudeStreamRenderer = class {
|
|
|
7021
7033
|
emit;
|
|
7022
7034
|
onEvent;
|
|
7023
7035
|
pending = "";
|
|
7036
|
+
collected = {};
|
|
7037
|
+
finished = false;
|
|
7024
7038
|
write(chunk) {
|
|
7025
7039
|
this.pending += chunk;
|
|
7026
7040
|
let nl = this.pending.indexOf("\n");
|
|
@@ -7032,11 +7046,16 @@ var ClaudeStreamRenderer = class {
|
|
|
7032
7046
|
}
|
|
7033
7047
|
/** Renders any trailing partial line. Call once the harness has exited. */
|
|
7034
7048
|
finish() {
|
|
7049
|
+
if (this.finished) return;
|
|
7050
|
+
this.finished = true;
|
|
7035
7051
|
if (this.pending) {
|
|
7036
7052
|
this.line(this.pending);
|
|
7037
7053
|
this.pending = "";
|
|
7038
7054
|
}
|
|
7039
7055
|
}
|
|
7056
|
+
summary() {
|
|
7057
|
+
return copySummary(this.collected);
|
|
7058
|
+
}
|
|
7040
7059
|
line(raw) {
|
|
7041
7060
|
const trimmed = raw.trim();
|
|
7042
7061
|
if (!trimmed) return;
|
|
@@ -7053,10 +7072,152 @@ var ClaudeStreamRenderer = class {
|
|
|
7053
7072
|
`);
|
|
7054
7073
|
return;
|
|
7055
7074
|
}
|
|
7075
|
+
if (!event || typeof event !== "object" || Array.isArray(event)) return;
|
|
7076
|
+
if (event.type === "result") this.collectResult(event);
|
|
7056
7077
|
this.onEvent?.(event);
|
|
7057
7078
|
for (const line of renderStreamEvent(event)) this.emit(`${line}
|
|
7058
7079
|
`);
|
|
7059
7080
|
}
|
|
7081
|
+
collectResult(event) {
|
|
7082
|
+
if (!this.collected.providerSessionId && validProviderSessionId(event.session_id)) {
|
|
7083
|
+
this.collected.providerSessionId = event.session_id;
|
|
7084
|
+
}
|
|
7085
|
+
if (validMetric(event.num_turns)) this.collected.numTurns = event.num_turns;
|
|
7086
|
+
if (validMetric(event.duration_ms)) this.collected.durationMs = event.duration_ms;
|
|
7087
|
+
const usage = { cost_source: "provider" };
|
|
7088
|
+
let measured = false;
|
|
7089
|
+
if (validMetric(event.total_cost_usd)) {
|
|
7090
|
+
usage.cost_usd = event.total_cost_usd;
|
|
7091
|
+
measured = true;
|
|
7092
|
+
}
|
|
7093
|
+
const raw = event.usage;
|
|
7094
|
+
if (raw && typeof raw === "object" && !Array.isArray(raw)) {
|
|
7095
|
+
for (const [source, destination] of [
|
|
7096
|
+
["input_tokens", "input_tokens"],
|
|
7097
|
+
["output_tokens", "output_tokens"],
|
|
7098
|
+
["cache_read_input_tokens", "cache_read_tokens"],
|
|
7099
|
+
["cache_creation_input_tokens", "cache_write_tokens"]
|
|
7100
|
+
]) {
|
|
7101
|
+
if (validMetric(raw[source])) {
|
|
7102
|
+
usage[destination] = raw[source];
|
|
7103
|
+
measured = true;
|
|
7104
|
+
}
|
|
7105
|
+
}
|
|
7106
|
+
}
|
|
7107
|
+
if (measured) this.collected.usage = usage;
|
|
7108
|
+
}
|
|
7109
|
+
};
|
|
7110
|
+
|
|
7111
|
+
// src/daemon/codex-stream.ts
|
|
7112
|
+
function object(value) {
|
|
7113
|
+
return value !== null && typeof value === "object" && !Array.isArray(value) ? value : void 0;
|
|
7114
|
+
}
|
|
7115
|
+
function text2(value) {
|
|
7116
|
+
return typeof value === "string" && value.trim() ? value.trim() : void 0;
|
|
7117
|
+
}
|
|
7118
|
+
function renderCodexEvent(event) {
|
|
7119
|
+
const type = event.type;
|
|
7120
|
+
if (type === "thread.started") return ["[session] started"];
|
|
7121
|
+
if (type === "turn.started") return ["[session] turn started"];
|
|
7122
|
+
if (type === "turn.completed") return ["[session] turn completed"];
|
|
7123
|
+
if (type === "turn.failed") {
|
|
7124
|
+
const error = object(event.error);
|
|
7125
|
+
return [`[error] ${clip(text2(error?.message) ?? text2(event.message) ?? "turn failed", 2e3)}`];
|
|
7126
|
+
}
|
|
7127
|
+
if (type === "error") return [`[error] ${clip(text2(event.message) ?? "unknown error", 2e3)}`];
|
|
7128
|
+
if (type !== "item.completed") return [];
|
|
7129
|
+
const item = object(event.item);
|
|
7130
|
+
if (!item) return [];
|
|
7131
|
+
switch (item.type) {
|
|
7132
|
+
case "agent_message":
|
|
7133
|
+
return text2(item.text) ? [`[agent] ${clip(text2(item.text), 2e3)}`] : [];
|
|
7134
|
+
case "command_execution": {
|
|
7135
|
+
const command = text2(item.command) ?? "command";
|
|
7136
|
+
const failed = validMetric(item.exit_code) && item.exit_code !== 0;
|
|
7137
|
+
const output = failed ? text2(item.aggregated_output) : void 0;
|
|
7138
|
+
return [
|
|
7139
|
+
`[tool] ${clip(command, 300)}${failed ? ` (exit ${item.exit_code}${output ? `: ${clip(output, 300)}` : ""})` : ""}`
|
|
7140
|
+
];
|
|
7141
|
+
}
|
|
7142
|
+
case "file_change": {
|
|
7143
|
+
const changes = Array.isArray(item.changes) ? item.changes : [];
|
|
7144
|
+
const paths = changes.map((change) => text2(object(change)?.path)).filter((path2) => Boolean(path2));
|
|
7145
|
+
return [`[tool] file change${paths.length ? `: ${clip(paths.join(", "), 300)}` : ""}`];
|
|
7146
|
+
}
|
|
7147
|
+
case "mcp_tool_call":
|
|
7148
|
+
return [
|
|
7149
|
+
`[tool] ${clip([text2(item.server), text2(item.tool)].filter(Boolean).join("/") || "MCP call", 300)}`
|
|
7150
|
+
];
|
|
7151
|
+
case "web_search":
|
|
7152
|
+
return [`[tool] web search${text2(item.query) ? `: ${clip(text2(item.query), 300)}` : ""}`];
|
|
7153
|
+
default:
|
|
7154
|
+
return [];
|
|
7155
|
+
}
|
|
7156
|
+
}
|
|
7157
|
+
var CodexStreamRenderer = class {
|
|
7158
|
+
constructor(emit) {
|
|
7159
|
+
this.emit = emit;
|
|
7160
|
+
}
|
|
7161
|
+
emit;
|
|
7162
|
+
pending = "";
|
|
7163
|
+
collected = {};
|
|
7164
|
+
finished = false;
|
|
7165
|
+
write(chunk) {
|
|
7166
|
+
this.pending += chunk;
|
|
7167
|
+
let nl = this.pending.indexOf("\n");
|
|
7168
|
+
while (nl !== -1) {
|
|
7169
|
+
this.line(this.pending.slice(0, nl));
|
|
7170
|
+
this.pending = this.pending.slice(nl + 1);
|
|
7171
|
+
nl = this.pending.indexOf("\n");
|
|
7172
|
+
}
|
|
7173
|
+
}
|
|
7174
|
+
finish() {
|
|
7175
|
+
if (this.finished) return;
|
|
7176
|
+
this.finished = true;
|
|
7177
|
+
if (this.pending) this.line(this.pending);
|
|
7178
|
+
this.pending = "";
|
|
7179
|
+
}
|
|
7180
|
+
summary() {
|
|
7181
|
+
return copySummary(this.collected);
|
|
7182
|
+
}
|
|
7183
|
+
line(raw) {
|
|
7184
|
+
const trimmed = raw.trim();
|
|
7185
|
+
if (!trimmed) return;
|
|
7186
|
+
let event;
|
|
7187
|
+
try {
|
|
7188
|
+
const parsed = JSON.parse(trimmed);
|
|
7189
|
+
const parsedObject = object(parsed);
|
|
7190
|
+
if (!parsedObject) return;
|
|
7191
|
+
event = parsedObject;
|
|
7192
|
+
} catch {
|
|
7193
|
+
this.emit(`${raw}
|
|
7194
|
+
`);
|
|
7195
|
+
return;
|
|
7196
|
+
}
|
|
7197
|
+
this.collect(event);
|
|
7198
|
+
for (const line of renderCodexEvent(event)) this.emit(`${line}
|
|
7199
|
+
`);
|
|
7200
|
+
}
|
|
7201
|
+
collect(event) {
|
|
7202
|
+
if (event.type === "thread.started" && !this.collected.providerSessionId && validProviderSessionId(event.thread_id)) {
|
|
7203
|
+
this.collected.providerSessionId = event.thread_id;
|
|
7204
|
+
}
|
|
7205
|
+
if (event.type !== "turn.completed") return;
|
|
7206
|
+
const raw = object(event.usage);
|
|
7207
|
+
if (!raw) return;
|
|
7208
|
+
const totalInput = validMetric(raw.input_tokens) ? raw.input_tokens : void 0;
|
|
7209
|
+
const rawCached = validMetric(raw.cached_input_tokens) ? raw.cached_input_tokens : void 0;
|
|
7210
|
+
const output = validMetric(raw.output_tokens) ? raw.output_tokens : void 0;
|
|
7211
|
+
if (totalInput === void 0 && rawCached === void 0 && output === void 0) return;
|
|
7212
|
+
const usage = {};
|
|
7213
|
+
if (totalInput !== void 0) {
|
|
7214
|
+
const cached = rawCached === void 0 ? void 0 : Math.min(rawCached, totalInput);
|
|
7215
|
+
usage.input_tokens = totalInput - (cached ?? 0);
|
|
7216
|
+
if (cached !== void 0) usage.cache_read_tokens = cached;
|
|
7217
|
+
} else if (rawCached !== void 0) usage.cache_read_tokens = rawCached;
|
|
7218
|
+
if (output !== void 0) usage.output_tokens = output;
|
|
7219
|
+
this.collected.usage = usage;
|
|
7220
|
+
}
|
|
7060
7221
|
};
|
|
7061
7222
|
|
|
7062
7223
|
// src/daemon/rate-limit.ts
|
|
@@ -7159,8 +7320,8 @@ function zonedDate(zone, at) {
|
|
|
7159
7320
|
return { y: get("year"), m: get("month") - 1, d: get("day") };
|
|
7160
7321
|
}
|
|
7161
7322
|
var DURATION = /(\d+)\s*([dhms])/g;
|
|
7162
|
-
function parseResetTime(
|
|
7163
|
-
const raw =
|
|
7323
|
+
function parseResetTime(text3, now) {
|
|
7324
|
+
const raw = text3.trim();
|
|
7164
7325
|
if (!raw) return null;
|
|
7165
7326
|
const relative = raw.match(/^in\s+(.+)$/i);
|
|
7166
7327
|
if (relative) {
|
|
@@ -7279,6 +7440,7 @@ function buildHarnessInvocation(spec, input) {
|
|
|
7279
7440
|
file: "codex",
|
|
7280
7441
|
args: [
|
|
7281
7442
|
"exec",
|
|
7443
|
+
"--json",
|
|
7282
7444
|
"--skip-git-repo-check",
|
|
7283
7445
|
...input.model ? ["--model", input.model] : [],
|
|
7284
7446
|
input.prompt
|
|
@@ -7432,9 +7594,9 @@ var LogBatcher = class {
|
|
|
7432
7594
|
seq = 0;
|
|
7433
7595
|
/** Chunks awaiting a successful send, oldest first. */
|
|
7434
7596
|
pending = [];
|
|
7435
|
-
append(
|
|
7436
|
-
if (!
|
|
7437
|
-
this.buffer +=
|
|
7597
|
+
append(text3) {
|
|
7598
|
+
if (!text3) return;
|
|
7599
|
+
this.buffer += text3;
|
|
7438
7600
|
if (Buffer.byteLength(this.buffer, "utf8") >= (this.opts.maxBytes ?? 32 * 1024)) {
|
|
7439
7601
|
void this.flush();
|
|
7440
7602
|
} else if (!this.timer) {
|
|
@@ -7924,10 +8086,13 @@ async function runDaemon(opts) {
|
|
|
7924
8086
|
const table2 = new RunTable(
|
|
7925
8087
|
{
|
|
7926
8088
|
finish: async (run, status, error, judgment) => {
|
|
8089
|
+
const summary = run.renderer?.summary();
|
|
7927
8090
|
await client2.finishRun(run.runId, {
|
|
7928
8091
|
status,
|
|
7929
8092
|
...error ? { error } : {},
|
|
7930
|
-
...judgment
|
|
8093
|
+
...judgment,
|
|
8094
|
+
usage: summary?.usage ?? { cost_source: "none" },
|
|
8095
|
+
...summary?.providerSessionId ? { provider_session_id: summary.providerSessionId } : {}
|
|
7931
8096
|
});
|
|
7932
8097
|
},
|
|
7933
8098
|
release: (run, { keep, outcome }) => {
|
|
@@ -7976,7 +8141,8 @@ async function runDaemon(opts) {
|
|
|
7976
8141
|
await client2.finishRun(orphan.run_id, {
|
|
7977
8142
|
status: "failed",
|
|
7978
8143
|
error: "daemon restarted; orphaned harness killed",
|
|
7979
|
-
judgment: "interrupted"
|
|
8144
|
+
judgment: "interrupted",
|
|
8145
|
+
usage: { cost_source: "none" }
|
|
7980
8146
|
});
|
|
7981
8147
|
} catch {
|
|
7982
8148
|
}
|
|
@@ -8107,23 +8273,38 @@ async function runDaemon(opts) {
|
|
|
8107
8273
|
(event) => limiter.noteStreamEvent(event)
|
|
8108
8274
|
);
|
|
8109
8275
|
run.renderer = renderer;
|
|
8110
|
-
run.drain = () => renderer.finish();
|
|
8111
8276
|
const spoolPath = join5(opts.configDir, "rawlogs", `${runId}.ndjson`);
|
|
8112
8277
|
mkdirSync4(dirname4(spoolPath), { recursive: true });
|
|
8113
8278
|
run.rawSpoolPath = spoolPath;
|
|
8114
8279
|
run.rawSpool = createWriteStream(spoolPath);
|
|
8115
8280
|
run.rawUpload = (body) => client2.putRunLogRaw(runId, body);
|
|
8281
|
+
const decoder = new StringDecoder("utf8");
|
|
8282
|
+
run.drain = () => {
|
|
8283
|
+
const trailing = decoder.end();
|
|
8284
|
+
if (trailing) renderer.write(trailing);
|
|
8285
|
+
renderer.finish();
|
|
8286
|
+
};
|
|
8116
8287
|
child.stdout?.on("data", (data) => {
|
|
8117
8288
|
run.rawSpool?.write(data);
|
|
8118
|
-
renderer.write(
|
|
8289
|
+
renderer.write(decoder.write(data));
|
|
8119
8290
|
});
|
|
8291
|
+
} else if (opts.harness === "codex") {
|
|
8292
|
+
const renderer = new CodexStreamRenderer((line) => run.batcher.append(line));
|
|
8293
|
+
const decoder = new StringDecoder("utf8");
|
|
8294
|
+
run.renderer = renderer;
|
|
8295
|
+
run.drain = () => {
|
|
8296
|
+
const trailing = decoder.end();
|
|
8297
|
+
if (trailing) renderer.write(trailing);
|
|
8298
|
+
renderer.finish();
|
|
8299
|
+
};
|
|
8300
|
+
child.stdout?.on("data", (data) => renderer.write(decoder.write(data)));
|
|
8120
8301
|
} else {
|
|
8121
8302
|
child.stdout?.on("data", (data) => run.batcher.append(data.toString("utf8")));
|
|
8122
8303
|
}
|
|
8123
8304
|
child.stderr?.on("data", (data) => {
|
|
8124
|
-
const
|
|
8125
|
-
run.batcher.append(
|
|
8126
|
-
run.limiter?.noteStderr(
|
|
8305
|
+
const text3 = data.toString("utf8");
|
|
8306
|
+
run.batcher.append(text3);
|
|
8307
|
+
run.limiter?.noteStderr(text3);
|
|
8127
8308
|
});
|
|
8128
8309
|
run.timeout = setTimeout(() => {
|
|
8129
8310
|
if (run.settled) return;
|
|
@@ -8135,7 +8316,7 @@ async function runDaemon(opts) {
|
|
|
8135
8316
|
child.on("error", (err) => {
|
|
8136
8317
|
void table2.finishAndCleanup(run, "failed", `failed to launch harness: ${message2(err)}`);
|
|
8137
8318
|
});
|
|
8138
|
-
child.on("
|
|
8319
|
+
child.on("close", (code, signal) => {
|
|
8139
8320
|
run.drain?.();
|
|
8140
8321
|
run.limiter?.finish();
|
|
8141
8322
|
const closing = exitLineForRun(run, {
|