teapot-coding-agent 0.37.55 → 0.37.56

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.
@@ -491,7 +491,9 @@ export function killBgShells(cwd) {
491
491
  return killed;
492
492
  }
493
493
  /** Run a command in its own process group; kill the whole group on timeout. */
494
- function runShell(cmd, ctx, timeoutMs) {
494
+ function runShell(cmd, ctx, timeoutMs,
495
+ /** did the caller pass `timeout_ms`, or is this the default? the message says which */
496
+ explicitTimeout = false) {
495
497
  return new Promise((resolve) => {
496
498
  const t0 = Date.now();
497
499
  // `bash -l` sources /etc/profile + ~/.bash_profile, either of which may
@@ -520,7 +522,35 @@ function runShell(cmd, ctx, timeoutMs) {
520
522
  // a crash, because nothing afterwards looks wrong.
521
523
  const killGroup = () => killTree(child);
522
524
  const timer = setTimeout(() => {
523
- killReason = `TIMEOUT after ${timeoutMs}ms — ${describeKill(killGroup())}`;
525
+ /**
526
+ * The message has to teach the agent what it did not know.
527
+ *
528
+ * It read `TIMEOUT after 120000ms — killed`, which names neither the limit in human terms nor
529
+ * the parameter that changes it. MEASURED: an agent that never noticed `timeout_ms` hit this
530
+ * repeatedly, retrying the same command. So the message says WHAT the limit was, WHERE it came
531
+ * from (default vs explicit), what to pass next time, and when to switch to `background`.
532
+ */
533
+ /**
534
+ * The message has to teach the agent what it did not know.
535
+ *
536
+ * It read `TIMEOUT after 120000ms — killed`, which names neither the limit in human terms nor
537
+ * the parameter that changes it. MEASURED: an agent that never noticed `timeout_ms` hit this
538
+ * repeatedly, retrying the same command. So the message says WHAT the limit was, WHERE it came
539
+ * from (default vs explicit), what to pass next time, and when to switch to `background`.
540
+ *
541
+ * The limit keeps its raw `ms` when it is sub-second: "TIMEOUT after 0s" for a 300ms limit
542
+ * both reads wrong and loses the precision a caller needs to judge its next attempt.
543
+ */
544
+ const limit = timeoutMs < 1000 ? `${timeoutMs}ms` : `${Math.round(timeoutMs / 1000)}s`;
545
+ killReason =
546
+ `TIMEOUT after ${limit} — ${describeKill(killGroup())}. ` +
547
+ (explicitTimeout
548
+ ? `You passed timeout_ms=${timeoutMs}${timeoutMs >= BASH_MAX_TIMEOUT_MS ? " (the maximum)" : ""}. `
549
+ : `This was the DEFAULT (${limit}) because no timeout_ms was given. `) +
550
+ (timeoutMs < BASH_MAX_TIMEOUT_MS
551
+ ? `Retry with a larger timeout_ms (up to ${BASH_MAX_TIMEOUT_MS}) if it legitimately needs longer, ` +
552
+ `or use background=true for a job with no timeout at all.`
553
+ : `If it needs longer than the maximum, use background=true — a background job has no timeout.`);
524
554
  }, timeoutMs);
525
555
  // harness shutdown must not wait out a long-running command
526
556
  const onAbort = () => {
@@ -684,7 +714,17 @@ export function disproportionateSpan(text, anchor) {
684
714
  }
685
715
  return null;
686
716
  }
687
- export const DEFAULT_TIMEOUT_MS = 120_000;
717
+ /**
718
+ * How long a `bash` command may run when `timeout_ms` is not given.
719
+ *
720
+ * The UI imports this (via `frontend/lib/bash-timeout.ts`) so the row shows the SAME cap the server
721
+ * enforces. They drifted once: the server applied 120s while the row drew nothing, because the row
722
+ * only looked at a `timeout_ms` the model had omitted.
723
+ */
724
+ export const BASH_DEFAULT_TIMEOUT_MS = 120_000;
725
+ /** the hard ceiling, applied after any `timeout_ms` the model passes */
726
+ export const BASH_MAX_TIMEOUT_MS = 600_000;
727
+ export const DEFAULT_TIMEOUT_MS = BASH_DEFAULT_TIMEOUT_MS;
688
728
  /* ---------- read_url cache ---------- */
689
729
  const URL_CACHE_TTL_MS = 3_600_000;
690
730
  const urlCache = new Map();
@@ -1798,7 +1838,15 @@ export const TOOLS = [
1798
1838
  type: "object",
1799
1839
  properties: {
1800
1840
  command: { type: "string" },
1801
- timeout_ms: { type: "number", description: `default ${DEFAULT_TIMEOUT_MS}` },
1841
+ timeout_ms: {
1842
+ type: "number",
1843
+ description: `How long the command may run before the harness KILLS it (whole process group, partial output ` +
1844
+ `returned). Default ${DEFAULT_TIMEOUT_MS / 1000}s, maximum 600s. ` +
1845
+ `MEASURED, an agent that does not know this burns several attempts on long builds: a command ` +
1846
+ `that needs more than the default MUST pass a larger timeout_ms (e.g. 300000 for a 5-minute ` +
1847
+ `build), and one that needs more than 600s must use background=true instead — there is no ` +
1848
+ `timeout on a background job.`,
1849
+ },
1802
1850
  background: {
1803
1851
  type: "boolean",
1804
1852
  description: "true = start the command and return IMMEDIATELY with a job id. You are NOTIFIED at your " +
@@ -1822,7 +1870,8 @@ export const TOOLS = [
1822
1870
  `Full output: bash_output(job_id="${id}"). Kill: bash_output(job_id="${id}", action="kill").`,
1823
1871
  };
1824
1872
  }
1825
- return runShell(str(args.command), ctx, Math.min(num(args.timeout_ms, ctx.defaultTimeoutMs), 600_000));
1873
+ const explicit = args.timeout_ms !== undefined && args.timeout_ms !== null;
1874
+ return runShell(str(args.command), ctx, Math.min(num(args.timeout_ms, ctx.defaultTimeoutMs), BASH_MAX_TIMEOUT_MS), explicit);
1826
1875
  },
1827
1876
  },
1828
1877
  {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "teapot-coding-agent",
3
- "version": "0.37.55",
3
+ "version": "0.37.56",
4
4
  "description": "A lightweight, always-on multi-agent harness for AI coding agents",
5
5
  "type": "module",
6
6
  "license": "AGPL-3.0-or-later",