tines 0.0.222 → 0.0.223

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 +86 -9
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -5623,6 +5623,15 @@ function runnerStatusLabel(runner) {
5623
5623
  return "rate limited";
5624
5624
  return runner.online ? "online" : "offline";
5625
5625
  }
5626
+ function runnerConcurrencyLabel(runner) {
5627
+ if (runner.type !== "local" || !runner.concurrency_control) return `${runner.max_concurrent}`;
5628
+ const control = runner.concurrency_control;
5629
+ if (control.status === "applied")
5630
+ return `${runner.max_concurrent} applied \xB7 ceiling ${control.ceiling ?? "?"}`;
5631
+ if (control.status === "pending")
5632
+ return `${runner.max_concurrent} pending \xB7 confirmed ${control.applied_cap ?? "none"} \xB7 ceiling ${control.ceiling ?? "?"}`;
5633
+ return `${runner.max_concurrent} web off (${control.reason ?? "unavailable"})`;
5634
+ }
5626
5635
  function ruleTargetsLabel(rule) {
5627
5636
  if (rule.targets.length === 0) return "(no targets)";
5628
5637
  if (rule.targets.length === 1 && rule.targets[0]?.runner_id === "*") {
@@ -10092,6 +10101,23 @@ function loadDaemonState(path2) {
10092
10101
  function saveDaemonState(path2, runs) {
10093
10102
  writeJsonFile(path2, { runs });
10094
10103
  }
10104
+ function daemonDeclinesPath(dir, runnerId) {
10105
+ return join5(dir, `daemon-declines-${runnerId}.json`);
10106
+ }
10107
+ function loadDaemonDeclines(path2) {
10108
+ const state = readJsonFile(path2);
10109
+ if (!state || !Array.isArray(state.run_ids)) return [];
10110
+ return [
10111
+ ...new Set(
10112
+ state.run_ids.filter(
10113
+ (id2) => typeof id2 === "string" && id2.length >= 1 && id2.length <= 128 && /^[\x21-\x7e]+$/.test(id2)
10114
+ )
10115
+ )
10116
+ ];
10117
+ }
10118
+ function saveDaemonDeclines(path2, runIds) {
10119
+ writeJsonFile(path2, { run_ids: [...runIds] });
10120
+ }
10095
10121
  function processStartTimeMs(pid) {
10096
10122
  try {
10097
10123
  const stat2 = readFileSync8(`/proc/${pid}/stat`, "utf8");
@@ -10198,7 +10224,7 @@ async function ensureRunnerCredentials(opts) {
10198
10224
  name: opts.name,
10199
10225
  harness: opts.harness,
10200
10226
  ...opts.command !== void 0 ? { command: opts.command } : {},
10201
- max_concurrent: opts.maxConcurrent,
10227
+ max_concurrent: opts.allowRemoteConcurrency ? 1 : opts.maxConcurrent,
10202
10228
  hostname: hostname(),
10203
10229
  platform: `${platform()} ${arch()}`
10204
10230
  });
@@ -10304,6 +10330,7 @@ async function runDaemon(opts) {
10304
10330
  harness: opts.harness,
10305
10331
  ...opts.command !== void 0 ? { command: opts.command } : {},
10306
10332
  maxConcurrent: opts.maxConcurrent,
10333
+ allowRemoteConcurrency: opts.allowRemoteConcurrency,
10307
10334
  ...opts.apiKey !== void 0 ? { apiKey: opts.apiKey } : {},
10308
10335
  log
10309
10336
  });
@@ -10316,7 +10343,12 @@ async function runDaemon(opts) {
10316
10343
  }
10317
10344
  const client2 = createApiClient({ baseUrl, apiKey: creds.token });
10318
10345
  const statePath = daemonStatePath(opts.configDir, creds.runner_id);
10346
+ const declinesPath = daemonDeclinesPath(opts.configDir, creds.runner_id);
10319
10347
  let shuttingDown = false;
10348
+ let effectiveConcurrency = opts.allowRemoteConcurrency ? 1 : opts.maxConcurrent;
10349
+ let appliedConcurrency;
10350
+ const declinedAssignments = new Set(loadDaemonDeclines(declinesPath));
10351
+ let warnedUnsupportedConcurrency = false;
10320
10352
  const refresher = new CliRefresher(() => installAgentCli({ configDir: opts.configDir, log }), {
10321
10353
  ttlMs: CLI_REFRESH_TTL_MS
10322
10354
  });
@@ -10764,7 +10796,7 @@ async function runDaemon(opts) {
10764
10796
  process.on("SIGINT", () => void shutdown());
10765
10797
  process.on("SIGTERM", () => void shutdown());
10766
10798
  log(
10767
- `polling ${baseUrl} every ${Math.round(opts.pollIntervalMs / 1e3)}s (harness ${opts.harness}, max ${opts.maxConcurrent} concurrent) \u2014 Ctrl-C to stop`
10799
+ `polling ${baseUrl} every ${Math.round(opts.pollIntervalMs / 1e3)}s (harness ${opts.harness}, ${opts.allowRemoteConcurrency ? `web-adjustable with local ceiling ${opts.maxConcurrent}` : `local cap ${opts.maxConcurrent}`}) \u2014 Ctrl-C to stop`
10768
10800
  );
10769
10801
  let failures = 0;
10770
10802
  while (!shuttingDown) {
@@ -10773,7 +10805,16 @@ async function runDaemon(opts) {
10773
10805
  const res = await client2.pollRunner(creds.runner_id, {
10774
10806
  instance_id: instanceId,
10775
10807
  owned_runs: table2.ids(),
10776
- max_concurrent: opts.maxConcurrent,
10808
+ // Kept for older servers. Opted-in daemons advertise only the last
10809
+ // accepted scheduling cap, never the higher machine ceiling.
10810
+ max_concurrent: effectiveConcurrency,
10811
+ concurrency_control: {
10812
+ version: 1,
10813
+ allow_remote: opts.allowRemoteConcurrency,
10814
+ ceiling: opts.maxConcurrent,
10815
+ ...appliedConcurrency ? { applied: appliedConcurrency } : {}
10816
+ },
10817
+ ...declinedAssignments.size > 0 ? { declined_assignments: [...declinedAssignments].slice(0, 100) } : {},
10777
10818
  ...effortCapabilities ? { effort_capabilities: effortCapabilities } : {},
10778
10819
  // Stated on every poll while pending; absent otherwise, which
10779
10820
  // the server reads as "not draining" — so a daemon that died
@@ -10782,11 +10823,31 @@ async function runDaemon(opts) {
10782
10823
  });
10783
10824
  failures = 0;
10784
10825
  for (const runId of res.cancels) killWithoutFinish(runId);
10826
+ let declinesChanged = false;
10827
+ for (const runId of res.released_assignments ?? []) {
10828
+ if (declinedAssignments.delete(runId)) declinesChanged = true;
10829
+ }
10830
+ if (declinesChanged) saveDaemonDeclines(declinesPath, declinedAssignments);
10831
+ if (res.concurrency_control) {
10832
+ const control = res.concurrency_control;
10833
+ if (opts.allowRemoteConcurrency && control.available && Number.isSafeInteger(control.revision) && Number.isInteger(control.cap) && control.cap >= 1 && control.cap <= opts.maxConcurrent) {
10834
+ effectiveConcurrency = control.cap;
10835
+ appliedConcurrency = { revision: control.revision, cap: control.cap };
10836
+ }
10837
+ } else if (opts.allowRemoteConcurrency && !warnedUnsupportedConcurrency) {
10838
+ warnedUnsupportedConcurrency = true;
10839
+ log(
10840
+ "web concurrency adjustment unavailable: upgrade the server; retaining conservative cap"
10841
+ );
10842
+ }
10785
10843
  for (const assignment of res.assignments) {
10786
- if (table2.size >= opts.maxConcurrent) {
10844
+ if (declinedAssignments.size > 0 || table2.size >= opts.maxConcurrent) {
10787
10845
  log(
10788
- `warning: supervisor delivered ${assignment.run.id} beyond --max-concurrent ${opts.maxConcurrent}; launching anyway (the server cap governs)`
10846
+ `run ${assignment.run.id}: refusing launch beyond local ceiling ${opts.maxConcurrent}; requesting safe release`
10789
10847
  );
10848
+ declinedAssignments.add(assignment.run.id);
10849
+ saveDaemonDeclines(declinesPath, declinedAssignments);
10850
+ continue;
10790
10851
  }
10791
10852
  void launch(assignment);
10792
10853
  }
@@ -10862,7 +10923,15 @@ function withDaemonFlags(cmd) {
10862
10923
  ).option("--harness <harness>", "claude-code | codex | custom", "claude-code").option(
10863
10924
  "--command <template>",
10864
10925
  "custom harness command template ({prompt_file}, {workspace}, {model})"
10865
- ).option("--max-concurrent <n>", "maximum simultaneous runs", (v) => Number.parseInt(v, 10), 1).option("--poll-interval <seconds>", "seconds between polls", (v) => Number.parseInt(v, 10), 15).option(
10926
+ ).option(
10927
+ "--max-concurrent <n>",
10928
+ "local maximum simultaneous runs (remote-adjustment ceiling when enabled)",
10929
+ (v) => Number(v),
10930
+ 1
10931
+ ).option(
10932
+ "--allow-remote-concurrency",
10933
+ "allow signed-in operators to request concurrency up to --max-concurrent"
10934
+ ).option("--poll-interval <seconds>", "seconds between polls", (v) => Number.parseInt(v, 10), 15).option(
10866
10935
  "--keep-workspaces <mode>",
10867
10936
  "keep settled runs' workspaces for debugging: never | failed | always",
10868
10937
  "never"
@@ -10910,6 +10979,7 @@ function parseDaemonFlags(opts) {
10910
10979
  harness,
10911
10980
  ...opts.command !== void 0 ? { command: opts.command } : {},
10912
10981
  maxConcurrent: opts.maxConcurrent,
10982
+ allowRemoteConcurrency: opts.allowRemoteConcurrency === true,
10913
10983
  pollIntervalSeconds: opts.pollInterval,
10914
10984
  keepWorkspaces,
10915
10985
  keepWorkspacesForHours: opts.keepWorkspacesFor,
@@ -10961,6 +11031,7 @@ function daemonArgs(input2) {
10961
11031
  ];
10962
11032
  if (input2.command !== void 0) args.push("--command", input2.command);
10963
11033
  if (input2.maxConcurrent !== 1) args.push("--max-concurrent", String(input2.maxConcurrent));
11034
+ if (input2.allowRemoteConcurrency) args.push("--allow-remote-concurrency");
10964
11035
  if (input2.pollIntervalSeconds !== void 0 && input2.pollIntervalSeconds !== 15)
10965
11036
  args.push("--poll-interval", String(input2.pollIntervalSeconds));
10966
11037
  if (input2.keepWorkspaces !== void 0 && input2.keepWorkspaces !== "never")
@@ -11228,6 +11299,7 @@ stop it first \u2014 \`tines runners pause ${settings.name}\`, wait for 0 active
11228
11299
  harness: settings.harness,
11229
11300
  ...settings.command !== void 0 ? { command: settings.command } : {},
11230
11301
  maxConcurrent: settings.maxConcurrent,
11302
+ allowRemoteConcurrency: settings.allowRemoteConcurrency,
11231
11303
  ...resolveApiKey(opts) !== void 0 ? { apiKey: resolveApiKey(opts) } : {},
11232
11304
  log: log2
11233
11305
  });
@@ -11258,6 +11330,7 @@ stop it first \u2014 \`tines runners pause ${settings.name}\`, wait for 0 active
11258
11330
  harness: harnessFlag(settings.harness),
11259
11331
  ...settings.command !== void 0 ? { command: settings.command } : {},
11260
11332
  maxConcurrent: settings.maxConcurrent,
11333
+ allowRemoteConcurrency: settings.allowRemoteConcurrency,
11261
11334
  pollIntervalSeconds: settings.pollIntervalSeconds,
11262
11335
  keepWorkspaces: settings.keepWorkspaces,
11263
11336
  keepWorkspacesForHours: settings.keepWorkspacesForHours,
@@ -11429,12 +11502,13 @@ function register8(program3) {
11429
11502
  if (opts.json) return printJson(res);
11430
11503
  if (res.items.length === 0) return console.log("no runners");
11431
11504
  table([
11432
- ["NAME", "TYPE", "STATUS", "RUNS", "TIER", "LAST SEEN"],
11505
+ ["NAME", "TYPE", "STATUS", "RUNS", "CONCURRENCY", "TIER", "LAST SEEN"],
11433
11506
  ...res.items.map((r) => [
11434
11507
  r.name,
11435
11508
  r.type,
11436
11509
  runnerStatusLabel(r),
11437
11510
  `${r.active_runs}/${r.max_concurrent}`,
11511
+ runnerConcurrencyLabel(r),
11438
11512
  r.default_tier,
11439
11513
  r.last_seen_at ? timestamp(r.last_seen_at) : "\u2014"
11440
11514
  ])
@@ -11449,6 +11523,7 @@ function register8(program3) {
11449
11523
  console.log(
11450
11524
  `active runs: ${runner.active_runs}/${runner.max_concurrent} timeout: ${runner.max_run_minutes}m default tier: ${runner.default_tier}`
11451
11525
  );
11526
+ console.log(`concurrency: ${runnerConcurrencyLabel(runner)}`);
11452
11527
  if (runner.last_seen_at) console.log(`last seen: ${timestamp(runner.last_seen_at)}`);
11453
11528
  if (runner.backoff_reason === "rate_limit" && runner.backoff_until) {
11454
11529
  console.log(
@@ -11691,6 +11766,7 @@ function register8(program3) {
11691
11766
  harness: settings.harness,
11692
11767
  command: settings.command,
11693
11768
  maxConcurrent: settings.maxConcurrent,
11769
+ allowRemoteConcurrency: settings.allowRemoteConcurrency,
11694
11770
  pollIntervalMs: settings.pollIntervalSeconds * 1e3,
11695
11771
  configDir: defaultConfigDir(),
11696
11772
  cliRefresh: opts.cliRefresh,
@@ -12178,7 +12254,7 @@ function queueFix(block) {
12178
12254
  const runner = block.runnerName ?? "the routed runner";
12179
12255
  switch (block.verdict) {
12180
12256
  case "at_capacity":
12181
- return `raise the cap on ${runner} \u2014 restart its daemon with tines runner daemon --max-concurrent N, or edit the runner on the Agents page`;
12257
+ return `edit ${runner} on the Agents page; if web adjustment is off, relaunch locally with tines runner daemon --allow-remote-concurrency --max-concurrent N`;
12182
12258
  case "quota_exhausted":
12183
12259
  return block.binding?.kind === "state_roster" ? "raise the roster limit \u2014 tines supervisor quota roster --default <n> --state <workflow>/<state>=<n> (this replaces the whole roster, so restate every override you keep)" : "raise the cap \u2014 tines supervisor quota global <n> \u2014 or switch to a per-state roster with tines supervisor quota roster";
12184
12260
  case "offline":
@@ -12283,7 +12359,8 @@ function register10(program3) {
12283
12359
  ` ${r.name}`,
12284
12360
  r.type,
12285
12361
  runnerStatusLabel(r),
12286
- `${r.active_runs}/${r.max_concurrent}`
12362
+ `${r.active_runs}/${r.max_concurrent}`,
12363
+ runnerConcurrencyLabel(r)
12287
12364
  ])
12288
12365
  );
12289
12366
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "tines",
3
- "version": "0.0.222",
3
+ "version": "0.0.223",
4
4
  "description": "CLI for Tines, an orchestration layer for AI agents",
5
5
  "repository": {
6
6
  "type": "git",