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.
Files changed (2) hide show
  1. package/dist/index.js +206 -25
  2. 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 text2 = helper.formatHelp(this, helper);
3134
- if (context.hasColors) return text2;
3135
- return this._outputConfiguration.stripColor(text2);
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, text2) {
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 text2 === "function") {
3305
- helpStr = text2({ error: context.error, command: context.command });
3304
+ if (typeof text3 === "function") {
3305
+ helpStr = text3({ error: context.error, command: context.command });
3306
3306
  } else {
3307
- helpStr = text2;
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 text2 = readBodyValue(markdown);
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: text2 });
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: text2.trim()
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: text2 });
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(text2, now) {
7163
- const raw = text2.trim();
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(text2) {
7436
- if (!text2) return;
7437
- this.buffer += text2;
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(data.toString("utf8"));
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 text2 = data.toString("utf8");
8125
- run.batcher.append(text2);
8126
- run.limiter?.noteStderr(text2);
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("exit", (code, signal) => {
8319
+ child.on("close", (code, signal) => {
8139
8320
  run.drain?.();
8140
8321
  run.limiter?.finish();
8141
8322
  const closing = exitLineForRun(run, {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "tines",
3
- "version": "0.0.165",
3
+ "version": "0.0.167",
4
4
  "description": "CLI for Tines, an orchestration layer for AI agents",
5
5
  "repository": {
6
6
  "type": "git",