tines 0.0.164 → 0.0.166

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 +212 -26
  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) {
@@ -6159,7 +6159,7 @@ files (v${artifact.current_version.version}):`);
6159
6159
  (v) => Number.parseInt(v, 10)
6160
6160
  ).option(
6161
6161
  "--out <path>",
6162
- "write to this file, or into this directory (keeps the stored filename)"
6162
+ "write to this file, or into this directory (keeps the stored filename); folder artifacts require a new or empty directory"
6163
6163
  )
6164
6164
  ).action(
6165
6165
  async (ref, name2, opts) => {
@@ -6184,6 +6184,11 @@ files (v${artifact.current_version.version}):`);
6184
6184
  if (existsSync3(opts.out) && !statSync2(opts.out).isDirectory()) {
6185
6185
  die(`--out for a folder must be a directory, and "${opts.out}" is a file`);
6186
6186
  }
6187
+ if (existsSync3(opts.out) && readdirSync(opts.out).length > 0) {
6188
+ die(
6189
+ `refusing to write folder artifact into non-empty directory "${opts.out}"; choose a new or empty directory`
6190
+ );
6191
+ }
6187
6192
  const files = version.files ?? [];
6188
6193
  let total = 0;
6189
6194
  for (const file of files) {
@@ -6361,12 +6366,12 @@ start one: tines journal append ${issue.project_name}/${issue.number} "- <date>:
6361
6366
  ).action(
6362
6367
  async (ref, markdown, opts, command) => {
6363
6368
  if (helpGuard(command, markdown)) return;
6364
- const text2 = readBodyValue(markdown);
6369
+ const text3 = readBodyValue(markdown);
6365
6370
  const api = client(opts);
6366
6371
  const { scope, note, item } = await resolveJournal(api, ref, opts.state);
6367
6372
  printNote(note);
6368
6373
  if (item) {
6369
- const updated = await api.appendContextItem(item.id, { text: text2 });
6374
+ const updated = await api.appendContextItem(item.id, { text: text3 });
6370
6375
  if (opts.json) return printJson(updated);
6371
6376
  return console.log(`appended to the ${scope.label} journal (now v${updated.version})`);
6372
6377
  }
@@ -6376,7 +6381,7 @@ start one: tines journal append ${issue.project_name}/${issue.number} "- <date>:
6376
6381
  name: JOURNAL_NAME,
6377
6382
  project_id: scope.project_id ?? void 0,
6378
6383
  workflow_state_id: scope.workflow_state_id ?? void 0,
6379
- body: text2.trim()
6384
+ body: text3.trim()
6380
6385
  });
6381
6386
  if (opts.json) return printJson(created);
6382
6387
  console.log(`started the ${scope.label} journal (${created.id})`);
@@ -6384,7 +6389,7 @@ start one: tines journal append ${issue.project_name}/${issue.number} "- <date>:
6384
6389
  if (!(err instanceof ApiError) || err.code !== "duplicate_context_name") throw err;
6385
6390
  const { item: fresh } = await resolveJournal(api, ref, opts.state);
6386
6391
  if (!fresh) throw err;
6387
- const updated = await api.appendContextItem(fresh.id, { text: text2 });
6392
+ const updated = await api.appendContextItem(fresh.id, { text: text3 });
6388
6393
  if (opts.json) return printJson(updated);
6389
6394
  console.log(`appended to the ${scope.label} journal (now v${updated.version})`);
6390
6395
  }
@@ -6933,6 +6938,7 @@ import {
6933
6938
  } from "node:fs";
6934
6939
  import { hostname, platform, arch } from "node:os";
6935
6940
  import { dirname as dirname4, join as join5 } from "node:path";
6941
+ import { StringDecoder } from "node:string_decoder";
6936
6942
 
6937
6943
  // src/version.ts
6938
6944
  import { readFileSync as readFileSync6 } from "node:fs";
@@ -6945,6 +6951,17 @@ function cliVersion() {
6945
6951
  }
6946
6952
  }
6947
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
+
6948
6965
  // src/daemon/claude-stream.ts
6949
6966
  function clip(value, max) {
6950
6967
  return value.length > max ? `${value.slice(0, max)}\u2026` : value;
@@ -7016,6 +7033,8 @@ var ClaudeStreamRenderer = class {
7016
7033
  emit;
7017
7034
  onEvent;
7018
7035
  pending = "";
7036
+ collected = {};
7037
+ finished = false;
7019
7038
  write(chunk) {
7020
7039
  this.pending += chunk;
7021
7040
  let nl = this.pending.indexOf("\n");
@@ -7027,11 +7046,16 @@ var ClaudeStreamRenderer = class {
7027
7046
  }
7028
7047
  /** Renders any trailing partial line. Call once the harness has exited. */
7029
7048
  finish() {
7049
+ if (this.finished) return;
7050
+ this.finished = true;
7030
7051
  if (this.pending) {
7031
7052
  this.line(this.pending);
7032
7053
  this.pending = "";
7033
7054
  }
7034
7055
  }
7056
+ summary() {
7057
+ return copySummary(this.collected);
7058
+ }
7035
7059
  line(raw) {
7036
7060
  const trimmed = raw.trim();
7037
7061
  if (!trimmed) return;
@@ -7048,10 +7072,152 @@ var ClaudeStreamRenderer = class {
7048
7072
  `);
7049
7073
  return;
7050
7074
  }
7075
+ if (!event || typeof event !== "object" || Array.isArray(event)) return;
7076
+ if (event.type === "result") this.collectResult(event);
7051
7077
  this.onEvent?.(event);
7052
7078
  for (const line of renderStreamEvent(event)) this.emit(`${line}
7053
7079
  `);
7054
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
+ }
7055
7221
  };
7056
7222
 
7057
7223
  // src/daemon/rate-limit.ts
@@ -7154,8 +7320,8 @@ function zonedDate(zone, at) {
7154
7320
  return { y: get("year"), m: get("month") - 1, d: get("day") };
7155
7321
  }
7156
7322
  var DURATION = /(\d+)\s*([dhms])/g;
7157
- function parseResetTime(text2, now) {
7158
- const raw = text2.trim();
7323
+ function parseResetTime(text3, now) {
7324
+ const raw = text3.trim();
7159
7325
  if (!raw) return null;
7160
7326
  const relative = raw.match(/^in\s+(.+)$/i);
7161
7327
  if (relative) {
@@ -7274,6 +7440,7 @@ function buildHarnessInvocation(spec, input) {
7274
7440
  file: "codex",
7275
7441
  args: [
7276
7442
  "exec",
7443
+ "--json",
7277
7444
  "--skip-git-repo-check",
7278
7445
  ...input.model ? ["--model", input.model] : [],
7279
7446
  input.prompt
@@ -7427,9 +7594,9 @@ var LogBatcher = class {
7427
7594
  seq = 0;
7428
7595
  /** Chunks awaiting a successful send, oldest first. */
7429
7596
  pending = [];
7430
- append(text2) {
7431
- if (!text2) return;
7432
- this.buffer += text2;
7597
+ append(text3) {
7598
+ if (!text3) return;
7599
+ this.buffer += text3;
7433
7600
  if (Buffer.byteLength(this.buffer, "utf8") >= (this.opts.maxBytes ?? 32 * 1024)) {
7434
7601
  void this.flush();
7435
7602
  } else if (!this.timer) {
@@ -7919,10 +8086,13 @@ async function runDaemon(opts) {
7919
8086
  const table2 = new RunTable(
7920
8087
  {
7921
8088
  finish: async (run, status, error, judgment) => {
8089
+ const summary = run.renderer?.summary();
7922
8090
  await client2.finishRun(run.runId, {
7923
8091
  status,
7924
8092
  ...error ? { error } : {},
7925
- ...judgment
8093
+ ...judgment,
8094
+ usage: summary?.usage ?? { cost_source: "none" },
8095
+ ...summary?.providerSessionId ? { provider_session_id: summary.providerSessionId } : {}
7926
8096
  });
7927
8097
  },
7928
8098
  release: (run, { keep, outcome }) => {
@@ -7971,7 +8141,8 @@ async function runDaemon(opts) {
7971
8141
  await client2.finishRun(orphan.run_id, {
7972
8142
  status: "failed",
7973
8143
  error: "daemon restarted; orphaned harness killed",
7974
- judgment: "interrupted"
8144
+ judgment: "interrupted",
8145
+ usage: { cost_source: "none" }
7975
8146
  });
7976
8147
  } catch {
7977
8148
  }
@@ -8102,23 +8273,38 @@ async function runDaemon(opts) {
8102
8273
  (event) => limiter.noteStreamEvent(event)
8103
8274
  );
8104
8275
  run.renderer = renderer;
8105
- run.drain = () => renderer.finish();
8106
8276
  const spoolPath = join5(opts.configDir, "rawlogs", `${runId}.ndjson`);
8107
8277
  mkdirSync4(dirname4(spoolPath), { recursive: true });
8108
8278
  run.rawSpoolPath = spoolPath;
8109
8279
  run.rawSpool = createWriteStream(spoolPath);
8110
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
+ };
8111
8287
  child.stdout?.on("data", (data) => {
8112
8288
  run.rawSpool?.write(data);
8113
- renderer.write(data.toString("utf8"));
8289
+ renderer.write(decoder.write(data));
8114
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)));
8115
8301
  } else {
8116
8302
  child.stdout?.on("data", (data) => run.batcher.append(data.toString("utf8")));
8117
8303
  }
8118
8304
  child.stderr?.on("data", (data) => {
8119
- const text2 = data.toString("utf8");
8120
- run.batcher.append(text2);
8121
- run.limiter?.noteStderr(text2);
8305
+ const text3 = data.toString("utf8");
8306
+ run.batcher.append(text3);
8307
+ run.limiter?.noteStderr(text3);
8122
8308
  });
8123
8309
  run.timeout = setTimeout(() => {
8124
8310
  if (run.settled) return;
@@ -8130,7 +8316,7 @@ async function runDaemon(opts) {
8130
8316
  child.on("error", (err) => {
8131
8317
  void table2.finishAndCleanup(run, "failed", `failed to launch harness: ${message2(err)}`);
8132
8318
  });
8133
- child.on("exit", (code, signal) => {
8319
+ child.on("close", (code, signal) => {
8134
8320
  run.drain?.();
8135
8321
  run.limiter?.finish();
8136
8322
  const closing = exitLineForRun(run, {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "tines",
3
- "version": "0.0.164",
3
+ "version": "0.0.166",
4
4
  "description": "CLI for Tines, an orchestration layer for AI agents",
5
5
  "repository": {
6
6
  "type": "git",