tines 0.0.209 → 0.0.210

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 (3) hide show
  1. package/README.md +2 -0
  2. package/dist/index.js +151 -24
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -45,6 +45,8 @@ tines issues comment <project>/<number> - <<'EOF' # body from stdin; @file al
45
45
  …
46
46
  EOF
47
47
  tines events list --issue <project>/<number>
48
+ tines events list --since 2026-09-01T00:00:00Z --until 2026-09-08T00:00:00Z --state Engineering/Review
49
+ tines supervisor stats --window 7d --project Tines
48
50
  ```
49
51
 
50
52
  Issues are addressed as `<project>/<number>`; schedules as `<project>/<name>`; workflow
package/dist/index.js CHANGED
@@ -3482,6 +3482,7 @@ function parsePrSpec(spec) {
3482
3482
  return repoUrl2 ? { repo_url: repoUrl2, number: Number.parseInt(url[2], 10) } : null;
3483
3483
  }
3484
3484
  var MODEL_TIERS = ["smartest", "balanced", "cheapest"];
3485
+ var RUN_END_OUTCOMES = ["advanced", "stalled", "interrupted"];
3485
3486
  var RUNNER_ONLINE_WINDOW_MS = 2 * 60 * 1e3;
3486
3487
  var DEFAULT_RESUME_WINDOW_HOURS = 48;
3487
3488
  var DEFAULT_RESUME_MAX_TURNS = 25;
@@ -3575,6 +3576,11 @@ function utilizationLabel(quota, activeRuns, stateName = (id2) => id2) {
3575
3576
  ([stateId, { name: name2, n }]) => `${name2} ${n}/${quota.overrides[stateId] ?? quota.default_limit}`
3576
3577
  ).join(" \xB7 ");
3577
3578
  }
3579
+ var RUN_OUTCOME_BUCKETS = [
3580
+ ...RUN_END_OUTCOMES,
3581
+ "failed",
3582
+ "unrecorded"
3583
+ ];
3578
3584
  var LIBRARY_MAX_BYTES = 5 * 1024 * 1024;
3579
3585
 
3580
3586
  // ../shared/src/events.ts
@@ -4183,7 +4189,9 @@ function createApiClient(options) {
4183
4189
  getPreferences: () => get("/api/v1/preferences"),
4184
4190
  updatePreferences: (body) => request("PATCH", "/api/v1/preferences", body),
4185
4191
  getSupervisorSettings: () => get("/api/v1/supervisor/settings"),
4186
- getSupervisorQueue: () => get("/api/v1/supervisor/queue"),
4192
+ getSupervisorQueue: (q = {}) => get(`/api/v1/supervisor/queue${query(q)}`),
4193
+ getSupervisorStats: (q = {}) => get(`/api/v1/supervisor/stats${query(q)}`),
4194
+ getSupervisorSentBack: (q) => get(`/api/v1/supervisor/stats/sent-back${query(q)}`),
4187
4195
  updateSupervisorSettings: (body) => request("PUT", "/api/v1/supervisor/settings", body),
4188
4196
  // API keys (create/revoke require a browser session, not a key)
4189
4197
  listApiKeys: (filters = {}) => get(`/api/v1/api-keys${query(filters)}`),
@@ -4426,6 +4434,38 @@ async function listAll(fetchPage, opts = {}) {
4426
4434
  // ../shared/src/routing.ts
4427
4435
  var INHERIT_RUNNER_ID = "*";
4428
4436
 
4437
+ // ../shared/src/stats-format.ts
4438
+ var MIN = 6e4;
4439
+ var HOUR = 60 * MIN;
4440
+ var DAY = 24 * HOUR;
4441
+ function durationLabel(ms) {
4442
+ if (ms === null || ms === void 0) return "\u2014";
4443
+ if (ms < MIN) return `${Math.round(ms / 1e3)} s`;
4444
+ if (ms < HOUR) return `${Math.round(ms / MIN)} min`;
4445
+ if (ms < DAY) return `${(ms / HOUR).toFixed(1)} h`;
4446
+ return `${(ms / DAY).toFixed(1)} d`;
4447
+ }
4448
+ function shareLabel(share) {
4449
+ if (share === null || share === void 0) return "\u2014";
4450
+ return `${Math.round(share * 100)}%`;
4451
+ }
4452
+ function deltaLabel(delta, kind) {
4453
+ if (delta === null || delta === void 0) return "";
4454
+ if (delta === 0) return "=";
4455
+ const arrow = delta > 0 ? "\u25B2" : "\u25BC";
4456
+ const size = Math.abs(delta);
4457
+ switch (kind) {
4458
+ case "ms":
4459
+ return `${arrow} ${durationLabel(size)}`;
4460
+ case "share":
4461
+ return `${arrow} ${Math.round(size * 100)} pp`;
4462
+ case "ratio":
4463
+ return `${arrow} ${size.toFixed(1)}`;
4464
+ default:
4465
+ return `${arrow} ${size}`;
4466
+ }
4467
+ }
4468
+
4429
4469
  // ../shared/src/issue-transfer.ts
4430
4470
  var compare = (a, b) => a < b ? -1 : a > b ? 1 : 0;
4431
4471
  function conflictsByKey(context2) {
@@ -8171,27 +8211,33 @@ function registerTime(program3) {
8171
8211
  function registerEvents(program3) {
8172
8212
  const events = program3.command("events").description("Read the activity log");
8173
8213
  withList(
8174
- events.command("list").description("List activity events, newest first").option("-i, --issue <ref>", "filter to one issue (<project>/<number>)").option("-p, --project <name>", "filter by project name or id").option("-t, --type <type>", "filter by event type (e.g. issue.transitioned)")
8175
- ).action(async (opts) => {
8176
- const api = client(opts);
8177
- const issueId = opts.issue ? (await resolveIssue(api, opts.issue)).id : void 0;
8178
- const res = await fetchList(
8179
- opts,
8180
- (page) => api.listEvents({
8181
- issue: issueId,
8182
- project: opts.project,
8183
- type: opts.type,
8184
- ...page
8185
- })
8186
- );
8187
- printList(res, opts, (items) => {
8188
- if (items.length === 0) return console.log("no events");
8189
- table([
8190
- ["WHEN", "ACTOR", "EVENT"],
8191
- ...items.map((ev) => [timestamp(ev.created_at), displayActor(ev), eventSummary(ev)])
8192
- ]);
8193
- });
8194
- });
8214
+ events.command("list").description("List activity events, newest first").option("-i, --issue <ref>", "filter to one issue (<project>/<number>)").option("-p, --project <name>", "filter by project name or id").option("-t, --type <type>", "filter by event type (e.g. issue.transitioned)").option("--since <time>", "inclusive lower bound (ISO 8601 or epoch ms)").option("--until <time>", "exclusive upper bound (ISO 8601 or epoch ms)").option("--state <workflow/state>", "events referencing a workflow state")
8215
+ ).action(
8216
+ async (opts) => {
8217
+ const api = client(opts);
8218
+ const issueId = opts.issue ? (await resolveIssue(api, opts.issue)).id : void 0;
8219
+ const stateId = opts.state ? (await resolveStateFlag(api, opts.state)).state.id : void 0;
8220
+ const res = await fetchList(
8221
+ opts,
8222
+ (page) => api.listEvents({
8223
+ issue: issueId,
8224
+ project: opts.project,
8225
+ type: opts.type,
8226
+ since: opts.since,
8227
+ until: opts.until,
8228
+ state: stateId,
8229
+ ...page
8230
+ })
8231
+ );
8232
+ printList(res, opts, (items) => {
8233
+ if (items.length === 0) return console.log("no events");
8234
+ table([
8235
+ ["WHEN", "ACTOR", "EVENT"],
8236
+ ...items.map((ev) => [timestamp(ev.created_at), displayActor(ev), eventSummary(ev)])
8237
+ ]);
8238
+ });
8239
+ }
8240
+ );
8195
8241
  }
8196
8242
 
8197
8243
  // src/commands/projects.ts
@@ -11834,10 +11880,26 @@ function queueFix(block) {
11834
11880
  return null;
11835
11881
  }
11836
11882
  }
11883
+ function outcomeLabel(stats) {
11884
+ const o = stats.current.runs.outcomes;
11885
+ const parts = [`adv ${o.advanced}`, `stalled ${o.stalled}`, `failed ${o.failed}`];
11886
+ if (o.interrupted > 0) parts.push(`intr ${o.interrupted}`);
11887
+ if (o.unrecorded > 0) parts.push(`unrecorded ${o.unrecorded}`);
11888
+ if (stats.current.runs.active > 0) parts.push(`active ${stats.current.runs.active}`);
11889
+ return parts.join(" \xB7 ");
11890
+ }
11891
+ function statsLevers() {
11892
+ return [
11893
+ "levers:",
11894
+ " queue wait tines supervisor quota roster --state <wf>/<state>=<n>",
11895
+ " runs tines runs list --state <wf>/<state>",
11896
+ " sent back tines context show <wf>/<state> instructions"
11897
+ ];
11898
+ }
11837
11899
  function register10(program3) {
11838
11900
  const supervisor = program3.command("supervisor").description("The automation kill switch, quota policy, and attempt limit");
11839
11901
  withCommon(
11840
- supervisor.command("status").description("One-screen overview: kill switch, quota, utilization, runners")
11902
+ supervisor.command("status").description("One-screen overview: kill switch, quota, utilization, runners").option("--project <name-or-id>", "filter waiting work to one project")
11841
11903
  ).action(async (opts) => {
11842
11904
  const api = client(opts);
11843
11905
  const [settings, runnersRes, workflows, activeRunItems, queue] = await Promise.all([
@@ -11845,7 +11907,7 @@ function register10(program3) {
11845
11907
  api.listRunners(),
11846
11908
  api.listWorkflows(),
11847
11909
  listAll((page) => api.listRuns({ active: true, ...page })),
11848
- api.getSupervisorQueue()
11910
+ api.getSupervisorQueue({ project: opts.project })
11849
11911
  ]);
11850
11912
  if (opts.json) {
11851
11913
  return printJson({
@@ -11902,6 +11964,71 @@ function register10(program3) {
11902
11964
  );
11903
11965
  }
11904
11966
  });
11967
+ withCommon(
11968
+ supervisor.command("stats").description("Per-stage flow this week: queue wait, work time, runs per visit, sent back").option("--window <window>", "Rolling window, e.g. 24h or 7d", "7d").option("--project <ref>", "Narrow to one project (id or name)").option("--sent-back <workflow/state>", "Show the issues behind one sent-back figure")
11969
+ ).action(async (opts) => {
11970
+ const api = client(opts);
11971
+ const project = opts.project ? await resolveProject(api, opts.project) : null;
11972
+ if (opts.sentBack) {
11973
+ const { state } = await resolveStateFlag(api, opts.sentBack);
11974
+ const detail = await api.getSupervisorSentBack({
11975
+ state: state.id,
11976
+ window: opts.window,
11977
+ project: project?.id ?? void 0
11978
+ });
11979
+ if (opts.json) return printJson(detail);
11980
+ console.log(`sent back from ${detail.state.workflow_name}/${detail.state.name}`);
11981
+ if (detail.prompt)
11982
+ console.log(`prompt: ${detail.prompt.name} (current v${detail.prompt.current_version})`);
11983
+ if (detail.items.length === 0) return console.log("no issues sent back");
11984
+ for (const item of detail.items) {
11985
+ console.log(
11986
+ `${item.issue.project_name}/${item.issue.number} \u2192 ${item.to_state_name} \xB7 prompt ${item.prompt_version ? `v${item.prompt_version}` : "unknown"} \xB7 ${item.actor.user_name}`
11987
+ );
11988
+ console.log(` ${item.comment?.excerpt.split("\n")[0] ?? "no comment"}`);
11989
+ }
11990
+ return;
11991
+ }
11992
+ const report = await api.getSupervisorStats({
11993
+ window: opts.window,
11994
+ project: project?.id ?? void 0
11995
+ });
11996
+ if (opts.json) return printJson(report);
11997
+ if (report.states.length === 0) {
11998
+ console.log("No agent stage saw work in the last window.");
11999
+ return;
12000
+ }
12001
+ const windowLabel = opts.window ?? "7d";
12002
+ console.log(
12003
+ `this week (${windowLabel}${report.project ? `, project ${report.project.name}` : ""})${report.previous ? ` \u2014 vs the ${windowLabel} before` : ""}`
12004
+ );
12005
+ table([
12006
+ [" stage", "visits", "queue p50", "p90", "work p50", "runs/visit", "ended", "sent back"],
12007
+ ...report.states.map((s) => [
12008
+ ` ${s.workflow_name}/${s.state_name}`,
12009
+ `${s.current.visits}\xB7${s.current.exits} ${deltaLabel(s.delta.visits, "count")}`.trim(),
12010
+ `${durationLabel(s.current.queue_wait?.p50 ?? null)} ${deltaLabel(s.delta.queue_wait_p50, "ms")}`.trim(),
12011
+ durationLabel(s.current.queue_wait?.p90 ?? null),
12012
+ `${durationLabel(s.current.work?.p50 ?? null)} ${deltaLabel(s.delta.work_p50, "ms")}`.trim(),
12013
+ s.current.runs.per_visit === null ? "\u2014" : `${s.current.runs.per_visit.toFixed(1)} ${deltaLabel(s.delta.runs_per_visit, "ratio")}`.trim(),
12014
+ outcomeLabel(s),
12015
+ `${s.current.sent_back.count} of ${s.current.exits} (${shareLabel(s.current.sent_back.share)}) \xB7 agents ${s.current.sent_back.agent} ${deltaLabel(s.delta.sent_back_share, "share")}`.trim()
12016
+ ])
12017
+ ]);
12018
+ if (report.markers.length > 0) {
12019
+ console.log("changes:");
12020
+ for (const marker of report.markers) {
12021
+ console.log(` ${marker.label}`);
12022
+ for (const effect of marker.effects) {
12023
+ const state = report.states.find((row) => row.state_id === effect.state_id);
12024
+ console.log(
12025
+ ` ${state?.state_name ?? effect.state_id}: since ${effect.after?.exits ?? 0} exits, ${shareLabel(effect.after?.sent_back_share)} sent back, queue ${durationLabel(effect.after?.queue_wait_p50)} \xB7 before ${effect.before?.exits ?? 0} exits, ${shareLabel(effect.before?.sent_back_share)} sent back, queue ${durationLabel(effect.before?.queue_wait_p50)}`
12026
+ );
12027
+ }
12028
+ }
12029
+ }
12030
+ for (const line of statsLevers()) console.log(line);
12031
+ });
11905
12032
  withCommon(
11906
12033
  supervisor.command("enable").description("Resume automation (the kill switch on)")
11907
12034
  ).action(async (opts) => {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "tines",
3
- "version": "0.0.209",
3
+ "version": "0.0.210",
4
4
  "description": "CLI for Tines, an orchestration layer for AI agents",
5
5
  "repository": {
6
6
  "type": "git",