UNPKG

@impulselab/hepha

Version:

Command-line interface for the Hepha platform.

4,706 lines • 148 kB
#!/usr/bin/env node

// ../../packages/core/src/tagged-error.ts
var TaggedError = class _TaggedError extends Error {
  _tag;
  constructor(tag, message, options) {
    super(message, options);
    this._tag = tag;
    this.name = tag;
  }
  static is(value) {
    return value instanceof Error && "_tag" in value && typeof value._tag === "string";
  }
  static hasTag(value, tag) {
    return _TaggedError.is(value) && value._tag === tag;
  }
};

// src/lib/shared/errors-constants.ts
var CLI_ERROR_CODES = {
  BAD_USAGE: "BAD_USAGE",
  /** The session needs a person: the exit-3 family. */
  NEEDS_HUMAN: "NEEDS_HUMAN",
  NOT_AUTHENTICATED: "NOT_AUTHENTICATED",
  NETWORK: "NETWORK",
  TIMEOUT: "TIMEOUT",
  /** Ctrl-C: a cancellation, not a failure of ours. */
  INTERRUPTED: "INTERRUPTED",
  /** A 2xx whose shape this CLI version does not understand. */
  API_SCHEMA_MISMATCH: "API_SCHEMA_MISMATCH",
  /** Not JSON at all: wrong host, proxy, error page. */
  NON_JSON_RESPONSE: "NON_JSON_RESPONSE",
  ERROR: "ERROR"
};

// src/lib/cli/cli-error.ts
var CliError = class extends TaggedError {
  exitCode;
  details;
  /**
   * A followed session parking on a question is a non-zero outcome AND a result
   * worth reading. Carried here so stdout stays ONE JSON document.
   */
  result;
  constructor(message, exitCode = 1, details = {}, result) {
    super("CliError", message);
    this.exitCode = exitCode;
    this.details = details;
    if (result !== void 0) {
      this.result = result;
    }
  }
  /** The code to report, defaulting to whatever the exit code implies. */
  get code() {
    if (this.details.code) {
      return this.details.code;
    }
    if (this.exitCode === 2) {
      return CLI_ERROR_CODES.BAD_USAGE;
    }
    if (this.exitCode === 3) {
      return CLI_ERROR_CODES.NEEDS_HUMAN;
    }
    return CLI_ERROR_CODES.ERROR;
  }
};

// ../../packages/database/schema/sessions/session-status-constants.ts
var SESSION_STATUS = {
  CREATED: "created",
  QUEUED: "queued",
  PROVISIONING: "provisioning",
  RUNNING: "running",
  WAITING_HUMAN: "waiting_human",
  AWAITING_APPROVAL: "awaiting_approval",
  WAITING_EXTERNAL: "waiting_external",
  AWAITING_DEVICE: "awaiting_device",
  DELIVERING: "delivering",
  VERIFYING: "verifying",
  COMPLETED: "completed",
  FAILED: "failed",
  KILLED: "killed",
  ABSTAINED: "abstained"
};
var SANDBOX_RETAINING_SESSION_STATUSES = [
  SESSION_STATUS.QUEUED,
  SESSION_STATUS.PROVISIONING,
  SESSION_STATUS.RUNNING,
  SESSION_STATUS.DELIVERING,
  SESSION_STATUS.VERIFYING
];

// src/lib/shared/output-constants.ts
var COLORS = {
  reset: "\x1B[0m",
  dim: "\x1B[2m",
  bold: "\x1B[1m",
  red: "\x1B[31m",
  green: "\x1B[32m",
  yellow: "\x1B[33m",
  blue: "\x1B[34m",
  magenta: "\x1B[35m"
};
var STATUS_COLORS = {
  [SESSION_STATUS.RUNNING]: "blue",
  [SESSION_STATUS.PROVISIONING]: "blue",
  [SESSION_STATUS.QUEUED]: "dim",
  [SESSION_STATUS.CREATED]: "dim",
  [SESSION_STATUS.DELIVERING]: "blue",
  [SESSION_STATUS.VERIFYING]: "blue",
  [SESSION_STATUS.WAITING_HUMAN]: "yellow",
  [SESSION_STATUS.WAITING_EXTERNAL]: "blue",
  [SESSION_STATUS.AWAITING_APPROVAL]: "yellow",
  [SESSION_STATUS.COMPLETED]: "green",
  [SESSION_STATUS.FAILED]: "red",
  [SESSION_STATUS.KILLED]: "red",
  [SESSION_STATUS.ABSTAINED]: "yellow"
};

// src/lib/ansi/strip-ansi.ts
function stripAnsi(text) {
  return text.replace(/\u001b\[\d+m/g, "");
}

// src/lib/supports/supports-color.ts
function supportsColor(jsonMode) {
  return process.stdout.isTTY === true && process.env.NO_COLOR === void 0 && !jsonMode;
}

// src/lib/cli/cli-output.ts
var CliOutput = class {
  jsonMode = false;
  /** Declared once per run, before anything can fail. */
  setJson(enabled) {
    this.jsonMode = enabled;
  }
  isJson() {
    return this.jsonMode;
  }
  json(value) {
    process.stdout.write(`${JSON.stringify(value, null, 2)}
`);
  }
  line(text = "") {
    process.stdout.write(`${text}
`);
  }
  error(text) {
    process.stderr.write(`${this.paint(`error: ${this.safe(text)}`, "red")}
`);
  }
  // Progress a long command owes the user; stderr keeps `--json` stdout whole.
  warn(text) {
    process.stderr.write(`${this.paint(this.safe(text), "yellow")}
`);
  }
  /**
   * The one place a failure is reported. Under `--json` stdout must be
   * parseable, and it was not: failures went to stderr as prose. Success keeps
   * its bare shape, so nothing that already parses stdout has to change.
   */
  failure(error) {
    if (!this.jsonMode) {
      this.error(error.message);
      return;
    }
    this.json({
      ok: false,
      error: {
        ...error.details.status === void 0 ? {} : { status: error.details.status },
        code: error.code,
        message: this.safe(error.message),
        ...error.details.issues ? { issues: error.details.issues } : {},
        ...error.details.retryAfterSeconds === void 0 ? {} : { retryAfterSeconds: error.details.retryAfterSeconds }
      },
      // Inside the envelope, so stdout stays one parseable document.
      ...error.result === void 0 ? {} : { result: error.result }
    });
  }
  paint(text, color) {
    if (!supportsColor(this.jsonMode)) {
      return text;
    }
    return `${COLORS[color] ?? ""}${text}${COLORS.reset}`;
  }
  /**
   * Neutralise terminal control sequences in text the platform gives us.
   *
   * Transcript content, PR titles and question bodies carry model output, tool
   * output and repository contents, none of it trusted. Printed raw, an ESC
   * sequence could repaint or erase what the user already read, or emit an OSC
   * 8 hyperlink pointing somewhere else entirely. Newlines and tabs survive;
   * every other C0/C1 control does not. (`--json` is unaffected: JSON.stringify
   * already escapes control characters.)
   */
  safe(text) {
    return text.replace(/[\u0000-\u0008\u000b-\u001f\u007f-\u009f]/g, "");
  }
  /** `safe`, flattened to one line: anything going into a table cell. */
  cell(text) {
    return this.safe(text).replace(/\s+/g, " ").trim();
  }
  status(status) {
    return this.paint(status, STATUS_COLORS[status] ?? "reset");
  }
  /** Left-aligned two-column block: the shape used by every list command. */
  table(rows) {
    if (rows.length === 0) {
      return;
    }
    const firstRow = rows[0];
    if (firstRow === void 0) {
      return;
    }
    const widths = firstRow.map(
      (_, column) => Math.max(...rows.map((row) => stripAnsi(row[column] ?? "").length))
    );
    for (const row of rows) {
      const line = row.map((cell, column) => {
        const width = widths[column];
        if (width === void 0) {
          return cell;
        }
        const padding = width - stripAnsi(cell).length;
        return column === row.length - 1 ? cell : cell + " ".repeat(padding);
      }).join("  ");
      this.line(line.trimEnd());
    }
  }
};

// src/lib/output/out.ts
var out = new CliOutput();

// src/lib/shared/config.ts
import { chmodSync, mkdirSync, rmSync, writeFileSync } from "fs";
import { dirname } from "path";

// src/lib/shared/config-constants.ts
var DEFAULT_BASE_URL = "https://hephaistos.impulselab.ai";

// src/lib/path/config-path-constants.ts
import { homedir } from "os";
import { join } from "path";
var CONFIG_PATH = join(homedir(), ".hepha", "config.json");

// src/lib/file/read-file-as-object.ts
import { readFileSync } from "fs";
function readFileAsObject() {
  try {
    const parsed = JSON.parse(readFileSync(CONFIG_PATH, "utf8"));
    if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
      return {};
    }
    return parsed;
  } catch {
    return {};
  }
}

// src/lib/string/config-read-string.ts
function configReadString(value) {
  return typeof value === "string" && value.trim() ? value.trim() : void 0;
}

// src/lib/user/read-user.ts
function readUser(value) {
  if (!value || typeof value !== "object") {
    return void 0;
  }
  const candidate = value;
  const user = {
    id: configReadString(candidate.id),
    name: configReadString(candidate.name),
    email: configReadString(candidate.email)
  };
  return user.id && user.name && user.email ? { id: user.id, name: user.name, email: user.email } : void 0;
}

// src/lib/http/is-http-url.ts
function isHttpUrl(value) {
  try {
    const parsed = new URL(value);
    return parsed.protocol === "http:" || parsed.protocol === "https:";
  } catch {
    return false;
  }
}

// src/lib/http/read-http-url.ts
function readHttpUrl(value) {
  const raw = configReadString(value);
  return raw && isHttpUrl(raw) ? raw : void 0;
}

// src/lib/same/is-same-origin.ts
function isSameOrigin(a, b) {
  try {
    return new URL(a).origin === new URL(b).origin;
  } catch {
    return false;
  }
}

// src/lib/shared/config.ts
var config = {
  path: CONFIG_PATH,
  load() {
    const stored = readFileAsObject();
    const storedBaseUrl = readHttpUrl(stored.baseUrl);
    const override = configReadString(process.env.HEPHA_URL);
    if (override && !readHttpUrl(override)) {
      throw new CliError(
        `HEPHA_URL is not a valid http(s) URL: "${override}".`,
        2
      );
    }
    const baseUrl = readHttpUrl(override) ?? storedBaseUrl ?? DEFAULT_BASE_URL;
    const token = storedBaseUrl && isSameOrigin(storedBaseUrl, baseUrl) ? configReadString(stored.token) : void 0;
    const user = token ? readUser(stored.user) : void 0;
    return {
      baseUrl,
      ...token ? { token } : {},
      ...user ? { user } : {}
    };
  },
  save(next) {
    mkdirSync(dirname(CONFIG_PATH), { recursive: true, mode: 448 });
    writeFileSync(CONFIG_PATH, `${JSON.stringify(next, null, 2)}
`, {
      mode: 384
    });
    chmodSync(CONFIG_PATH, 384);
  },
  clear() {
    rmSync(CONFIG_PATH, { force: true });
  }
};

// src/lib/version/read-version.ts
import { readFileSync as readFileSync2 } from "fs";
import { dirname as dirname2, join as join2 } from "path";
import { fileURLToPath } from "url";
function readVersion() {
  let directory = dirname2(fileURLToPath(import.meta.url));
  for (let depth = 0; depth < 5; depth += 1) {
    try {
      const manifest = JSON.parse(
        readFileSync2(join2(directory, "package.json"), "utf8")
      );
      const version = manifest.version;
      if (typeof version === "string" && version.length > 0) {
        return version;
      }
    } catch {
    }
    const parent = dirname2(directory);
    if (parent === directory) {
      break;
    }
    directory = parent;
  }
  return "unknown";
}

// src/lib/version/version-constants.ts
var VERSION = readVersion();

// src/commands/help/help-constants.ts
var HELP = `hepha: orchestrate Hephaistos sessions from a terminal or an agent.

USAGE
  hepha <command> [args] [--flags]

THE LOOP
  hepha run "<prompt>" --repo owner/repo --follow
      Start a session and follow it until it finishes or asks you something.
      Exit 0 completed \xB7 3 waiting on a human \xB7 1 failed.
      [--title "\u2026"] [--task-type implement|new-project|orchestrator|design|automate]
      [--model <slug>] [--harness claude-code|codex] [--branch <name>]
      [--skill <id>]\u2026
  hepha questions list [--session <id>]
  hepha questions show <batchId>
  hepha questions answer <batchId> --option <questionId>=1,2 --text <questionId>="\u2026"
                                   [--answers '<json array>']
  hepha questions cancel <batchId>
  hepha approvals list | show <id> | approve <id> | reject <id> --mode rework --feedback "\u2026"

SESSIONS
  hepha sessions list [--status active|waiting|done] [--repo owner/repo]
                      [--search "\u2026"] [--page N] [--limit N] [--all]
  hepha sessions get <id>
  hepha sessions logs <id> [--after <sequence>] [--limit N] [--follow]
  hepha sessions send <id> "<message>"
  hepha sessions pause|resume|trash <id>
  hepha sessions kill <id> [--reason "\u2026"]
  hepha sessions checks|pr|verify <id>

CATALOG
  hepha repos
  hepha branches <owner/repo>
  hepha models
  hepha skills

AUTH
  hepha login [--url https://\u2026]     Device authorization: approve it in a browser.
  hepha logout
  hepha whoami
  hepha keys list
  hepha keys create <name> [--scope sessions:read]\u2026 [--expires-in-days 90]
  hepha keys revoke <keyId>

ENVIRONMENT
  HEPHA_API_KEY     Act as an agent. Takes precedence over a stored login, so
                    the human-only commands (approvals approve/reject, keys)
                    answer 403 while it is set; unset it to act as yourself.
  HEPHA_TOKEN       A device-flow session token, instead of ~/.hepha/config.json.
  HEPHA_URL         Platform base URL.
  HEPHA_TIMEOUT_MS  Per-request timeout in milliseconds (default 30000).
  HEPHA_FOLLOW_MAX_OUTAGE_SECONDS
                    Give up this long after --follow's first failure, with that
                    failure as the exit code. Unset, it waits for the platform
                    to come back.

GLOBAL FLAGS
  --json          Machine-readable output on stdout. Every command that
                  returns data honours it; login is interactive and does not.
                  Failures print {"ok":false,"error":{\u2026}} and keep their exit code.
  --quiet         Suppress the streamed transcript while following a session.
  --version       Print the version and exit.
  --help          Print this.

Unknown flags and surplus arguments are refused: for an agent, a typo must
fail rather than run with default options.

`;

// src/commands/command/help-command.ts
function helpCommand() {
  const baseUrl = config.load().baseUrl.replace(/\/$/, "");
  out.line(`${HELP}hepha ${VERSION} \xB7 full reference: ${baseUrl}/api/v1/docs`);
}

// src/lib/args/args-constants.ts
var GLOBAL_BOOLEANS = ["json", "help", "quiet"];

// src/lib/boolean/boolean-flags-constants.ts
var BOOLEAN_FLAGS = /* @__PURE__ */ new Set([
  ...GLOBAL_BOOLEANS,
  "follow",
  "all",
  "version"
]);

// src/lib/args/parse-flag.ts
function parseFlag(argv, index, parsed) {
  const token = argv[index];
  if (token === void 0) {
    return index;
  }
  const withoutDashes = token.slice(2);
  const equalsAt = withoutDashes.indexOf("=");
  let name;
  let value;
  if (equalsAt >= 0) {
    name = withoutDashes.slice(0, equalsAt);
    value = withoutDashes.slice(equalsAt + 1);
    if (BOOLEAN_FLAGS.has(name)) {
      throw new CliError(
        `--${name} is a boolean flag: pass --${name} to enable it, or omit it. "--${name}=${value}" is not a value.`,
        2
      );
    }
  } else {
    name = withoutDashes;
    const next = argv[index + 1];
    if (!BOOLEAN_FLAGS.has(name) && next !== void 0 && !next.startsWith("--")) {
      value = next;
      index += 1;
    }
  }
  if (value === void 0 || value.trim() === "") {
    if (!BOOLEAN_FLAGS.has(name)) {
      throw new CliError(`--${name} expects a value.`, 2);
    }
    parsed.booleans.add(name);
    return index;
  }
  parsed.flags[name] = value;
  (parsed.repeated[name] ??= []).push(value);
  return index;
}

// src/lib/args/args-parser.ts
var ArgsParser = class {
  parse(argv) {
    const parsed = {
      positional: [],
      flags: {},
      repeated: {},
      booleans: /* @__PURE__ */ new Set()
    };
    let flagsEnded = false;
    for (let index = 0; index < argv.length; index += 1) {
      const token = argv[index];
      if (token === void 0) {
        break;
      }
      if (!flagsEnded && token === "--") {
        flagsEnded = true;
        continue;
      }
      if (flagsEnded || !token.startsWith("--")) {
        parsed.positional.push(token);
        continue;
      }
      index = parseFlag(argv, index, parsed);
    }
    return parsed;
  }
  /**
   * Refuse anything this action does not understand. Called once the action is
   * known, because `sessions list` and `sessions logs` accept different flags.
   */
  expect(parsed, spec) {
    const allowed = /* @__PURE__ */ new Set([
      ...GLOBAL_BOOLEANS,
      ...spec.flags ?? [],
      ...spec.booleans ?? []
    ]);
    for (const name of [...Object.keys(parsed.flags), ...parsed.booleans]) {
      if (!allowed.has(name)) {
        throw new CliError(`Unknown flag --${name}.`, 2);
      }
    }
    const max = spec.positional ?? 0;
    if (parsed.positional.length > max) {
      const surplus = parsed.positional.slice(max).map((value) => `"${value}"`).join(", ");
      throw new CliError(
        `Unexpected argument${parsed.positional.length - max > 1 ? "s" : ""} ${surplus}.`,
        2
      );
    }
  }
  /** The action name, validated against what the group actually implements. */
  action(parsed, group, allowed, fallback) {
    const action = parsed.positional[0] ?? fallback;
    if (!allowed.includes(action)) {
      throw new CliError(
        `Unknown ${group} command "${action}". Expected one of: ${allowed.join(", ")}.`,
        2
      );
    }
    return action;
  }
  required(parsed, index, name) {
    const value = parsed.positional[index];
    if (!value) {
      throw new CliError(`Missing <${name}>.`, 2);
    }
    return value;
  }
  /**
   * A whole number inside a range, refused locally: `--page -1` used to come
   * back from the API as a bare "Input validation failed".
   */
  integer(parsed, name, bounds = {}) {
    const raw = parsed.flags[name];
    if (raw === void 0) {
      return void 0;
    }
    const trimmed2 = raw.trim();
    const value = Number(trimmed2);
    if (trimmed2 === "" || !Number.isFinite(value)) {
      throw new CliError(`--${name} expects a number, got "${raw}".`, 2);
    }
    if (!Number.isInteger(value)) {
      throw new CliError(`--${name} expects a whole number, got "${raw}".`, 2);
    }
    if (bounds.min !== void 0 && value < bounds.min) {
      throw new CliError(`--${name} must be at least ${bounds.min}.`, 2);
    }
    if (bounds.max !== void 0 && value > bounds.max) {
      throw new CliError(`--${name} must be at most ${bounds.max}.`, 2);
    }
    return value;
  }
  /** One of a fixed set, named in the error so the fix is obvious. */
  choice(parsed, name, allowed) {
    const raw = parsed.flags[name];
    if (raw === void 0) {
      return void 0;
    }
    if (!allowed.includes(raw)) {
      throw new CliError(
        `--${name} expects one of: ${allowed.join(", ")}. Got "${raw}".`,
        2
      );
    }
    return raw;
  }
};

// src/lib/args/args.ts
var args = new ArgsParser();

// src/lib/wants/wants-json.ts
function wantsJson(argv) {
  for (const token of argv) {
    if (token === "--") {
      return false;
    }
    if (token === "--json") {
      return true;
    }
  }
  return false;
}

// src/lib/api/api-constants.ts
var DEFAULT_TIMEOUT_MS = 3e4;

// src/lib/timeout/resolve-timeout.ts
function resolveTimeout(override) {
  if (override !== void 0) {
    return override;
  }
  const raw = process.env.HEPHA_TIMEOUT_MS?.trim();
  if (!raw) {
    return DEFAULT_TIMEOUT_MS;
  }
  const parsed = Number(raw);
  if (!Number.isFinite(parsed) || parsed <= 0) {
    throw new CliError(
      `HEPHA_TIMEOUT_MS expects a positive number of milliseconds, got "${raw}".`,
      2
    );
  }
  return parsed;
}

// src/lib/auth/auth-headers.ts
function authHeaders(as) {
  if (as) {
    return { authorization: `Bearer ${as.token}` };
  }
  const apiKey = process.env.HEPHA_API_KEY;
  if (apiKey) {
    return { "x-api-key": apiKey };
  }
  const token = process.env.HEPHA_TOKEN ?? config.load().token;
  if (token) {
    return { authorization: `Bearer ${token}` };
  }
  throw new CliError(
    "Not signed in. Run `hepha login`, or set HEPHA_API_KEY to act as an agent.",
    1,
    { code: CLI_ERROR_CODES.NOT_AUTHENTICATED }
  );
}

// src/lib/version/client-headers.ts
function clientHeaders() {
  return { "x-hepha-client": `hepha-cli/${VERSION}` };
}

// src/lib/retry/retry-after-seconds.ts
function retryAfterSeconds(response) {
  const raw = response.headers.get("retry-after") ?? response.headers.get("x-retry-after");
  if (!raw) {
    return void 0;
  }
  const seconds = Number(raw.trim());
  if (Number.isFinite(seconds)) {
    return seconds >= 0 ? seconds : void 0;
  }
  const date = Date.parse(raw.trim());
  return Number.isNaN(date) ? void 0 : Math.max(0, (date - Date.now()) / 1e3);
}

// src/lib/response/fetch-response.ts
async function fetchResponse({
  url,
  method,
  headers,
  body,
  deadline,
  baseUrl
}) {
  const controller = new AbortController();
  let abortedBy = null;
  const timer = setTimeout(() => {
    abortedBy ??= "timeout";
    controller.abort();
  }, deadline);
  const onInterrupt = () => {
    abortedBy ??= "interrupt";
    controller.abort();
  };
  process.once("SIGINT", onInterrupt);
  try {
    const response = await fetch(url, {
      method,
      headers,
      signal: controller.signal,
      ...body === void 0 ? {} : { body: JSON.stringify(body) }
    });
    return [response, await response.text()];
  } catch (cause) {
    if (abortedBy === "interrupt") {
      throw new CliError(`${method} ${url.pathname} interrupted.`, 130, {
        code: CLI_ERROR_CODES.INTERRUPTED
      });
    }
    if (abortedBy === "timeout") {
      throw new CliError(
        `${method} ${url.pathname} timed out after ${deadline}ms. Raise HEPHA_TIMEOUT_MS if the platform is simply slow.`,
        1,
        { code: CLI_ERROR_CODES.TIMEOUT }
      );
    }
    throw new CliError(
      `Cannot reach ${baseUrl}. Set HEPHA_URL if the platform lives elsewhere. (${String(cause)})`,
      1,
      { code: CLI_ERROR_CODES.NETWORK }
    );
  } finally {
    clearTimeout(timer);
    process.removeListener("SIGINT", onInterrupt);
  }
}

// src/lib/api/add-query-parameters.ts
function addQueryParameters(url, query) {
  for (const [key, value] of Object.entries(query ?? {})) {
    if (value !== void 0) {
      url.searchParams.set(key, String(value));
    }
  }
}

// src/lib/excerpt/excerpt.ts
function excerpt(text) {
  const flattened = out.cell(text);
  return flattened.length > 200 ? `${flattened.slice(0, 200)}\u2026` : flattened;
}

// src/lib/api/parse-response-payload.ts
function parseResponsePayload(response, text, request) {
  const { method, url, retryAfter } = request;
  if (!text) {
    return null;
  }
  try {
    return JSON.parse(text);
  } catch {
    const body = excerpt(text);
    const contentType = response.headers.get("content-type") ?? "unknown";
    throw new CliError(
      response.ok ? `${method} ${url.pathname} answered ${response.status} with a non-JSON body (${contentType}). Is HEPHA_URL pointing at a Hephaistos deployment?${body ? ` Body: ${body}` : ""}` : `${method} ${url.pathname} failed with ${response.status} and a non-JSON body (${contentType}).${body ? ` Body: ${body}` : ""}`,
      1,
      {
        status: response.status,
        code: CLI_ERROR_CODES.NON_JSON_RESPONSE,
        ...retryAfter === void 0 ? {} : { retryAfterSeconds: retryAfter }
      }
    );
  }
}

// src/lib/issues/read-issues.ts
function readIssues(payload) {
  if (!payload || typeof payload !== "object") {
    return void 0;
  }
  const data = payload.data;
  if (!data || typeof data !== "object") {
    return void 0;
  }
  const issues = data.issues;
  return Array.isArray(issues) ? issues : void 0;
}

// ../../packages/core/src/api/path-not-served-constants.ts
var PATH_NOT_SERVED = "path_not_served";

// src/lib/string/read-string.ts
function readString(payload, key) {
  if (!payload || typeof payload !== "object") {
    return void 0;
  }
  const value = payload[key];
  return typeof value === "string" && value.length > 0 ? value : void 0;
}

// src/lib/message/message-from.ts
function messageFrom(status, payload) {
  const message = readString(payload, "message");
  if (status === 404 && readString(payload, "error") === PATH_NOT_SERVED && message) {
    return `${message} hepha ${VERSION} may be ahead of the platform.`;
  }
  if (message) {
    return status === 403 && process.env.HEPHA_API_KEY ? `${message} (HEPHA_API_KEY is set and takes precedence over your \`hepha login\` session; unset it to act as yourself.)` : message;
  }
  if (status === 401) {
    return "Unauthorized: your credential is missing, expired or revoked.";
  }
  return `Request failed with status ${status}.`;
}

// src/lib/api/throw-response-error.ts
function throwResponseError(response, payload, retryAfter) {
  const code = readString(payload, "code") ?? readString(payload, "error");
  const issues = readIssues(payload);
  throw new CliError(
    messageFrom(response.status, payload) + (response.status === 429 && retryAfter !== void 0 ? ` Retry in ${retryAfter}s.` : ""),
    1,
    {
      status: response.status,
      ...code ? { code } : {},
      ...issues ? { issues } : {},
      ...retryAfter === void 0 ? {} : { retryAfterSeconds: retryAfter }
    }
  );
}

// src/lib/api/api.ts
async function api(requestPath, options = {}) {
  const baseUrl = options.as?.baseUrl ?? config.load().baseUrl;
  const url = new URL(`${baseUrl.replace(/\/$/, "")}/api/v1${requestPath}`);
  addQueryParameters(url, options.query);
  const headers = {
    ...clientHeaders(),
    ...authHeaders(options.as),
    ...options.body === void 0 ? {} : { "content-type": "application/json" }
  };
  const method = options.method ?? "GET";
  const deadline = resolveTimeout(options.timeoutMs);
  const [response, text] = await fetchResponse({
    url,
    method,
    headers,
    body: options.body,
    deadline,
    baseUrl
  });
  const retryAfter = retryAfterSeconds(response);
  const payload = parseResponsePayload(response, text, {
    method,
    url,
    retryAfter
  });
  if (!response.ok) {
    throwResponseError(response, payload, retryAfter);
  }
  return payload;
}

// src/lib/path/path.ts
function path(strings, ...values) {
  return strings.reduce(
    (accumulator, literal, index) => accumulator + (index === 0 ? "" : encodeURIComponent(values[index - 1] ?? "")) + literal,
    ""
  );
}

// src/lib/read/read.ts
function read(payload, dotted) {
  return dotted.split(".").reduce((current, key) => {
    if (!current || typeof current !== "object") {
      return void 0;
    }
    return current[key];
  }, payload);
}

// src/lib/expect/expect-shape.ts
function expectShape(payload, endpoint, requirements) {
  for (const [field, predicate] of Object.entries(requirements)) {
    if (!predicate(read(payload, field))) {
      throw new CliError(
        `${endpoint} returned a payload this CLI does not understand: "${field}" is missing or has the wrong type. hepha ${VERSION} may be out of step with the platform; try \`npm install -g @impulselab/hepha\`.`,
        1,
        { code: CLI_ERROR_CODES.API_SCHEMA_MISMATCH }
      );
    }
  }
  return payload;
}

// src/lib/is/is.ts
var is = {
  string: (value) => typeof value === "string",
  // Absent passes: a field a deployment older than this binary does not send.
  optionalString: (value) => value === void 0 || typeof value === "string",
  number: (value) => typeof value === "number",
  array: (value) => Array.isArray(value),
  object: (value) => Boolean(value) && typeof value === "object" && !Array.isArray(value)
};

// src/commands/approvals/approvals-constants.ts
var ACTIONS = ["list", "show", "approve", "reject"];
var REJECT_MODES = ["rework", "terminate"];

// src/commands/approvals/list-approvals.ts
async function listApprovals(asJson) {
  const result = expectShape(
    await api("/approvals"),
    "GET /approvals",
    { items: is.array }
  );
  if (asJson) {
    out.json(result);
    return;
  }
  if (result.items.length === 0) {
    out.line("No guardrail waiting on you.");
    return;
  }
  out.table(
    result.items.map((item) => [
      out.cell(item.id),
      out.cell(item.guardrailKind),
      out.cell(item.repoName ?? "-"),
      out.cell(item.summary)
    ])
  );
}

// src/commands/command/approvals-command.ts
async function approvalsCommand(parsed) {
  const action = args.action(parsed, "approvals", ACTIONS, "list");
  const asJson = parsed.booleans.has("json");
  if (action === "list") {
    args.expect(parsed, { positional: 1 });
    return await listApprovals(asJson);
  }
  args.expect(parsed, {
    positional: 2,
    ...action === "reject" ? { flags: ["mode", "feedback"] } : {}
  });
  const guardrailId = args.required(parsed, 1, "guardrailRequestId");
  if (action === "show") {
    const detail2 = expectShape(
      await api(path`/approvals/${guardrailId}`),
      "GET /approvals/{id}",
      { guardrailKind: is.string, status: is.string, summary: is.string }
    );
    if (asJson) {
      out.json(detail2);
      return;
    }
    out.line(
      `${out.paint(out.cell(detail2.guardrailKind), "bold")}  ${detail2.status}`
    );
    out.line(out.safe(detail2.summary));
    out.line();
    out.json(detail2.payload);
    return;
  }
  if (action === "approve") {
    const result2 = await api(
      path`/approvals/${guardrailId}/approve`,
      { method: "POST", body: {} }
    );
    if (asJson) {
      out.json(result2);
      return;
    }
    out.line(`${out.paint("\u2713", "green")} Approved ${out.cell(guardrailId)}`);
    return;
  }
  const mode = args.choice(parsed, "mode", REJECT_MODES) ?? "rework";
  if (mode === "rework" && !parsed.flags.feedback) {
    throw new CliError(
      "--feedback is required with --mode rework: the agent needs to know what to change.",
      2
    );
  }
  const result = await api(
    path`/approvals/${guardrailId}/reject`,
    {
      method: "POST",
      body: {
        mode,
        ...parsed.flags.feedback ? { feedback: parsed.flags.feedback } : {}
      }
    }
  );
  if (asJson) {
    out.json(result);
    return;
  }
  out.line(
    `${out.paint("\u2713", "green")} Rejected ${out.cell(guardrailId)} (${mode})`
  );
}

// src/commands/catalog/list-catalog-branches.ts
async function listCatalogBranches(parsed, asJson) {
  args.expect(parsed, { positional: 1 });
  const repoFullName = args.required(parsed, 0, "owner/repo");
  const segments2 = repoFullName.split("/");
  const [owner, repo] = segments2;
  if (segments2.length !== 2 || !owner || !repo) {
    throw new CliError(
      `Expected a repository as "owner/repo", got "${repoFullName}".`,
      2
    );
  }
  const result = await api(
    path`/repos/${owner}/${repo}/branches`
  );
  if (!result.result) {
    throw new CliError(`No GitHub installation covers ${repoFullName}.`);
  }
  if (asJson) {
    out.json(result);
    return;
  }
  for (const branch of result.result.branches) {
    out.line(
      branch === result.result.defaultBranch ? `${out.cell(branch)} ${out.paint("(default)", "dim")}` : out.cell(branch)
    );
  }
}

// src/commands/catalog/list-catalog-models.ts
async function listCatalogModels(parsed, asJson) {
  args.expect(parsed, {});
  const result = expectShape(
    await api("/models"),
    "GET /models",
    { harnesses: is.array, gatewayModels: is.array }
  );
  if (asJson) {
    out.json(result);
    return;
  }
  out.line(out.paint("harnesses", "dim"));
  for (const harness of result.harnesses) {
    out.line(`  ${out.cell(harness)}`);
  }
  out.line();
  out.line(out.paint("gateway models", "dim"));
  for (const model of result.gatewayModels) {
    out.line(`  ${out.cell(model.id)}`);
  }
}

// src/commands/catalog/read-catalog-list.ts
async function readCatalogList(parsed, path2, field) {
  args.expect(parsed, {});
  return expectShape(await api(path2), `GET ${path2}`, { [field]: is.array });
}

// src/commands/catalog/list-catalog-repos.ts
async function listCatalogRepos(parsed, asJson) {
  const result = await readCatalogList(
    parsed,
    "/repos",
    "repos"
  );
  if (asJson) {
    out.json(result);
    return;
  }
  for (const repo of result.repos) {
    out.line(out.cell(repo));
  }
}

// src/commands/catalog/list-catalog-skills.ts
async function listCatalogSkills(parsed, asJson) {
  const result = await readCatalogList(
    parsed,
    "/skills",
    "skills"
  );
  if (asJson) {
    out.json(result);
    return;
  }
  out.table(
    result.skills.map((skill) => [
      out.cell(skill.id),
      out.cell(skill.name),
      out.cell(skill.description ?? "")
    ])
  );
}

// src/commands/catalog/catalog-topics.ts
var catalogTopics = {
  branches: listCatalogBranches,
  models: listCatalogModels,
  repos: listCatalogRepos,
  skills: listCatalogSkills
};

// src/commands/command/catalog-command.ts
async function catalogCommand(topic, parsed) {
  const list = catalogTopics[topic];
  if (!list) {
    throw new CliError(`Unknown catalog topic "${topic}".`, 2);
  }
  await list(parsed, parsed.booleans.has("json"));
}

// src/commands/keys/keys-constants.ts
var ACTIONS2 = ["list", "create", "revoke"];

// src/commands/key/create-key.ts
async function createKey(parsed, asJson) {
  args.expect(parsed, { flags: ["scope", "expires-in-days"], positional: 2 });
  const name = args.required(parsed, 1, "name");
  const scopes = parsed.repeated.scope;
  const expiresInDays = args.integer(parsed, "expires-in-days", {
    min: 1,
    max: 365
  });
  const created = expectShape(
    await api("/keys", {
      method: "POST",
      body: {
        name,
        ...scopes && scopes.length > 0 ? { scopes } : {},
        ...expiresInDays !== void 0 ? { expiresInDays } : {}
      }
    }),
    "POST /keys",
    { key: is.string, scopes: is.array }
  );
  if (asJson) {
    out.json(created);
    return;
  }
  out.line(
    `${out.paint("\u2713", "green")} Created "${out.cell(created.name ?? name)}"`
  );
  out.line();
  out.line(`  ${out.paint(created.key, "bold")}`);
  out.line();
  out.line(
    out.paint(
      "  Copy it now; it is hashed at rest and never shown again.",
      "dim"
    )
  );
  out.line(out.paint(`  Scopes: ${created.scopes.join(" ")}`, "dim"));
}

// src/commands/command/keys-command.ts
async function keysCommand(parsed) {
  const action = args.action(parsed, "keys", ACTIONS2, "list");
  const asJson = parsed.booleans.has("json");
  if (action === "list") {
    args.expect(parsed, { positional: 1 });
    const result2 = expectShape(
      await api("/keys"),
      "GET /keys",
      { keys: is.array }
    );
    if (asJson) {
      out.json(result2);
      return;
    }
    if (result2.keys.length === 0) {
      out.line("No keys. Create one with `hepha keys create <name>`.");
      return;
    }
    out.table([
      [
        out.paint("ID", "dim"),
        out.paint("NAME", "dim"),
        out.paint("PREFIX", "dim"),
        out.paint("SCOPES", "dim")
      ],
      ...result2.keys.map((key) => [
        out.cell(key.id),
        out.cell(key.name ?? "-"),
        out.cell(key.start ?? "-"),
        key.scopes.join(" ")
      ])
    ]);
    return;
  }
  if (action === "create") {
    return await createKey(parsed, asJson);
  }
  args.expect(parsed, { positional: 2 });
  const keyId = args.required(parsed, 1, "keyId");
  const result = await api(path`/keys/${keyId}`, {
    method: "DELETE"
  });
  if (asJson) {
    out.json(result);
    return;
  }
  out.line(`${out.paint("\u2713", "green")} Revoked ${out.cell(keyId)}`);
}

// src/commands/command/login-command.ts
import { createAuthClient } from "better-auth/client";
import { deviceAuthorizationClient } from "better-auth/client/plugins";

// src/lib/token/device-token-error.ts
var deviceTokenError = {
  /** What the server said, when it said anything at all. */
  message(error) {
    if (!error || typeof error !== "object") {
      return void 0;
    }
    const message = error.message;
    return typeof message === "string" && message.length > 0 ? message : void 0;
  },
  /** Better Auth answers `X-Retry-After`, the platform the standard header. */
  retryAfter(error) {
    if (!error || typeof error !== "object") {
      return void 0;
    }
    const headers = error.headers;
    if (!(headers instanceof Headers)) {
      return void 0;
    }
    const raw = headers.get("x-retry-after") ?? headers.get("retry-after") ?? void 0;
    if (!raw) {
      return void 0;
    }
    const seconds = Number(raw.trim());
    return Number.isFinite(seconds) && seconds > 0 ? seconds : void 0;
  }
};

// src/lib/retry/next-poll-delay.ts
function nextPollDelay(interval, remainingSeconds) {
  if (remainingSeconds <= 0) {
    return null;
  }
  return Math.min(interval, remainingSeconds);
}

// src/lib/browser/open-browser.ts
import { spawn } from "child_process";
function openBrowser(url) {
  const command = process.platform === "darwin" ? "open" : process.platform === "win32" ? "cmd" : "xdg-open";
  const commandArgs = process.platform === "win32" ? ["/c", "start", "", url] : [url];
  try {
    const child = spawn(command, commandArgs, {
      stdio: "ignore",
      detached: true
    });
    child.on("error", () => {
    });
    child.unref();
  } catch {
  }
}

// src/commands/login/login-constants.ts
var MIN_BACKOFF = 10;

// ../../packages/database/shared/constants.ts
var USER_ROLES = {
  ADMIN: "admin",
  MEMBER: "member",
  GUEST: "guest"
};
var ALL_USER_ROLES = Object.values(USER_ROLES);
var API_SCOPES = [
  "sessions:read",
  "sessions:export",
  "sessions:write",
  "questions:read",
  "questions:answer",
  "approvals:read",
  "catalog:read",
  "pull_requests:read",
  "devices:read",
  "devices:write",
  "browser:read",
  "browser:write",
  "accounts:read",
  "inference:write"
];
var DEFAULT_API_SCOPES = API_SCOPES.filter(
  (scope) => scope.endsWith(":read") && !scope.startsWith("browser:")
);
var SESSION = {
  /** Cookie + session row lifetime: 30 days. */
  EXPIRES_IN: 60 * 60 * 24 * 30,
  /** Refresh the expiry at most once a day, to avoid a write per request. */
  UPDATE_AGE: 60 * 60 * 24,
  /** Cache the session row in memory for this long, to avoid a read per request. */
  COOKIE_CACHE_MAX_AGE: 60
};
var DEVICE_AUTHORIZATION = {
  /** Only these clients may open a device flow. */
  CLIENT_IDS: ["hepha-cli", "hepha-extension"],
  /** Lifetime of an unapproved user code. */
  EXPIRES_IN: "15m",
  /** Minimum delay the CLI must wait between two token polls. */
  INTERVAL: "5s",
  /** Page a human visits to approve a pending device. */
  VERIFICATION_PATH: "/device",
  /**
   * Per-IP ceiling on `/device/token`. The window is deliberately SHORTER than
   * INTERVAL: Better Auth only resets its counter when two requests are further
   * apart than the window, so a 5s poll under the default 60s window never reset
   * it and every login 429'd partway through the code's lifetime.
   */
  POLL_RATE_LIMIT: { WINDOW_SECONDS: 4, MAX_REQUESTS: 10 }
};
var AUTO_REBASE_CLAIM_MS = 30 * 60 * 1e3;

// src/commands/client/client-id-constants.ts
var CLIENT_ID = DEVICE_AUTHORIZATION.CLIENT_IDS[0];

// src/commands/sleep/sleep.ts
function sleep(seconds) {
  return new Promise((resolve) => setTimeout(resolve, seconds * 1e3));
}

// src/commands/complete/complete-login.ts
async function completeLogin(baseUrl, token) {
  const identity = await api("/me", {
    as: { baseUrl, token }
  });
  config.save({
    baseUrl,
    token,
    user: {
      id: identity.user.id,
      name: identity.user.name,
      email: identity.user.email
    }
  });
  out.line();
  out.line(
    `${out.paint("\u2713", "green")} Signed in as ${identity.user.email} on ${baseUrl}`
  );
  out.line(out.paint(`  Session stored in ${config.path}`, "dim"));
}

// src/commands/login/resolve-login-base-url.ts
function resolveLoginBaseUrl(parsed) {
  const requested = parsed.flags.url?.trim();
  if (requested !== void 0 && !isHttpUrl(requested)) {
    throw new CliError(`--url is not a valid http(s) URL: "${requested}".`, 2);
  }
  return (requested ?? config.load().baseUrl).replace(/\/$/, "");
}

// src/commands/command/login-command.ts
async function loginCommand(parsed) {
  args.expect(parsed, { flags: ["url"] });
  const baseUrl = resolveLoginBaseUrl(parsed);
  const authClient = createAuthClient({
    baseURL: baseUrl,
    plugins: [deviceAuthorizationClient()]
  });
  const { data: device, error: deviceError } = await authClient.device.code({
    client_id: CLIENT_ID,
    scope: "openid profile email"
  });
  if (deviceError || !device) {
    throw new CliError(
      deviceError?.error_description ?? `Could not start a device login against ${baseUrl}.`
    );
  }
  const verificationUrl = device.verification_uri_complete ?? device.verification_uri;
  out.line();
  out.line(`  Code   ${out.paint(out.cell(device.user_code), "bold")}`);
  out.line(`  Open   ${out.cell(verificationUrl)}`);
  out.line();
  out.line(out.paint("  Waiting for approval\u2026", "dim"));
  openBrowser(verificationUrl);
  let interval = device.interval ?? 5;
  const deadline = Date.now() + (device.expires_in ?? 900) * 1e3;
  while (Date.now() < deadline) {
    const delay = nextPollDelay(interval, (deadline - Date.now()) / 1e3);
    if (delay === null) {
      break;
    }
    await sleep(delay);
    const { data: token, error } = await authClient.device.token({
      grant_type: "urn:ietf:params:oauth:grant-type:device_code",
      device_code: device.device_code,
      client_id: CLIENT_ID
    });
    if (error?.status === 429) {
      interval = Math.max(
        deviceTokenError.retryAfter(error) ?? interval * 2,
        MIN_BACKOFF
      );
      continue;
    }
    if (token?.access_token) {
      await completeLogin(baseUrl, token.access_token);
      return;
    }
    if (!error) {
      continue;
    }
    switch (error.error) {
      case "authorization_pending":
        continue;
      case "slow_down":
        interval += 5;
        continue;
      case "access_denied":
        throw new CliError("Access denied: the request was rejected.");
      default:
        throw new CliError(
          error.error_description || deviceTokenError.message(error) || `Device authorization failed (HTTP ${error.status}).`
        );
    }
  }
  throw new CliError("The code expired before it was approved. Try again.");
}

// src/commands/command/logout-command.ts
function logoutCommand(parsed) {
  args.expect(parsed, {});
  config.clear();
  if (parsed.booleans.has("json")) {
    out.json({ signedOut: true });
    return;
  }
  out.line("Signed out.");
}

// src/commands/answers/collect-answers-constants.ts
var LIMITS = {
  QUESTION_ID: 40,
  OPTION_ID: 40,
  OPTIONS_PER_ANSWER: 50,
  FREE_TEXT: 1e4,
  ANSWERS: 50
};

// src/commands/answer/validate-free-text.ts
function validateFreeText(value, label) {
  if (value === void 0) {
    return;
  }
  const freeText = value.trim();
  if (freeText.length === 0) {
    throw new CliError(`${label} has empty free text.`, 2);
  }
  if (freeText.length > LIMITS.FREE_TEXT) {
    throw new CliError(
      `${label} has free text longer than ${LIMITS.FREE_TEXT} characters.`,
      2
    );
  }
}

// src/commands/answer/validate-selected-option-ids.ts
function validateSelectedOptionIds(options, label) {
  if (options === void 0) {
    return;
  }
  if (options.length > LIMITS.OPTIONS_PER_ANSWER) {
    throw new CliError(
      `${label} selects more than ${LIMITS.OPTIONS_PER_ANSWER} options.`,
      2
    );
  }
  for (const id of options) {
    if (id.trim().length === 0) {
      throw new CliError(`${label} has an empty option id.`, 2);
    }
    if (id.trim().length > LIMITS.OPTION_ID) {
      throw new CliError(
        `${label} has an option id longer than ${LIMITS.OPTION_ID} characters.`,
        2
      );
    }
  }
}

// src/commands/answer/validate-answer.ts
function validateAnswer(answer, label) {
  if (answer.questionId.trim().length === 0) {
    throw new CliError(`${label} needs a non-empty "questionId".`, 2);
  }
  if (answer.questionId.length > LIMITS.QUESTION_ID) {
    throw new CliError(
      `${label} has a "questionId" longer than ${LIMITS.QUESTION_ID} characters.`,
      2
    );
  }
  const options = answer.selectedOptionIds;
  validateSelectedOptionIds(options, label);
  validateFreeText(answer.freeText, label);
  if ((options === void 0 || options.length === 0) && answer.freeText === void 0) {
    throw new CliError(
      `${label} answers nothing. Give it at least one option id or some free text.`,
      2
    );
  }
}

// src/commands/answers/parse-answer-entry.ts
function parseAnswerEntry(entry, label) {
  if (!entry || typeof entry !== "object" || Array.isArray(entry)) {
    throw new CliError(`${label} must be an object.`, 2);
  }
  const answer = entry;
  if (typeof answer.questionId !== "string") {
    throw new CliError(`${label} needs a non-empty "questionId".`, 2);
  }
  if (answer.selectedOptionIds !== void 0 && (!Array.isArray(answer.selectedOptionIds) || answer.selectedOptionIds.some((id) => typeof id !== "string"))) {
    throw new CliError(
      `${label}.selectedOptionIds must be an array of strings.`,
      2
    );
  }
  if (answer.freeText !== void 0 && typeof answer.freeText !== "string") {
    throw new CliError(`${label}.freeText must be a string.`, 2);
  }
  const parsed = entry;
  validateAnswer(parsed, label);
  return parsed;
}

// src/commands/answers/parse-answers-flag.ts
function parseAnswersFlag(raw) {
  let decoded;
  try {
    decoded = JSON.parse(raw);
  } catch {
    throw new CliError("--answers expects a JSON array.", 2);
  }
  if (!Array.isArray(decoded) || decoded.length === 0) {
    throw new CliError(
      `--answers expects a non-empty JSON array, e.g. '[{"questionId":"q1","selectedOptionIds":["1"]}]'.`,
      2
    );
  }
  if (decoded.length > LIMITS.ANSWERS) {
    throw new CliError(
      `--answers carries more than ${LIMITS.ANSWERS} entries.`,
      2
    );
  }
  for (const [index, entry] of decoded.entries()) {
    parseAnswerEntry(entry, `--answers[${index}]`);
  }
  return decoded;
}

// src/commands/answers/collect-answers.ts
function collectAnswers(parsed) {
  if (parsed.flags.answers) {
    return parseAnswersFlag(parsed.flags.answers);
  }
  const byQuestion = /* @__PURE__ */ new Map();
  const put = (pair, apply) => {
    const equalsAt = pair.indexOf("=");
    if (equalsAt < 0) {
      throw new CliError(`Expected <questionId>=<value>, got "${pair}".`, 2);
    }
    const questionId = pair.slice(0, equalsAt);
    const entry = byQuestion.get(questionId) ?? { questionId };
    apply(entry);
    byQuestion.set(questionId, entry);
  };
  for (const pair of parsed.repeated.option ?? []) {
    put(pair, (entry) => {
      entry.selectedOptionIds = pair.slice(pair.indexOf("=") + 1).split(",").map((id) => id.trim()).filter(Boolean);
    });
  }
  for (const pair of parsed.repeated.text ?? []) {
    put(pair, (entry) => {
      entry.freeText = pair.slice(pair.indexOf("=") + 1);
    });
  }
  const answers = [...byQuestion.values()];
  if (answers.length === 0) {
    throw new CliError(
      "Nothing to submit. Use --option <questionId>=<ids>, --text <questionId>=<answer>, or --answers <json>.",
      2
    );
  }
  if (answers.length > LIMITS.ANSWERS) {
    throw new CliError(
      `More than ${LIMITS.ANSWERS} answers in one submission.`,
      2
    );
  }
  for (const answer of answers) {
    validateAnswer(answer, `--option/--text ${answer.questionId}`);
  }
  return answers;
}

// src/commands/questions/questions-constants.ts
var ACTIONS3 = ["list", "show", "answer", "cancel"];

// src/commands/questions/list-questions.ts
async function listQuestions(sessionId, asJson) {
  const result = expectShape(
    await api(
      sessionId ? path`/sessions/${sessionId}/questions` : "/questions"
    ),
    "GET /questions",
    { items: is.array }
  );
  if (asJson) {
    out.json(result);
    return;
  }
  if (result.items.length === 0) {
    out.line("Nothing waiting on you.");
    return;
  }
  out.table(
    result.items.map((batch) => [
      out.cell(batch.id),
      out.cell(batch.sessionId),
      `${batch.questions.length} question(s)`,
      out.cell(batch.sessionTitle)
    ])
  );
}

// src/commands/batch/print-batch-question.ts
function printBatchQuestion(question) {
  out.line();
  if (question.header) {
    out.line(out.paint(out.cell(question.header), "dim"));
  }
  out.line(
    `${out.paint(out.cell(question.id), "dim")} ${out.safe(question.body)}`
  );
  if (question.image) {
    out.line(out.paint(`    (image) ${out.cell(question.image)}`, "dim"));
  }
  for (const option of question.options) {
    const recommended = option.id === question.recommendedOptionId;
    const label = option.label ? out.cell(option.label) : option.image ? out.paint(`(image) ${out.cell(option.image)}`, "dim") : out.paint("(no label)", "dim");
    out.line(
      `    ${out.cell(option.id)}) ${label}${recommended ? out.paint("  \u2190 recommended", "green") : ""}`
    );
  }
  if (question.allowFreeText) {
    out.line(out.paint("    free text accepted", "dim"));
  }
}

// src/commands/batch/print-batch.ts
function printBatch(batch) {
  out.line(
    `${out.paint(out.cell(batch.sessionTitle), "bold")}  ${out.paint(`round ${batch.round}`, "dim")}`
  );
  out.line(
    out.paint(
      `batch ${out.cell(batch.id)} \xB7 session ${out.cell(batch.sessionId)}`,
      "dim"
    )
  );
  for (const question of batch.questions) {
    printBatchQuestion(question);
  }
}

// src/commands/command/questions-command.ts
async function questionsCommand(parsed) {
  const action = args.action(parsed, "questions", ACTIONS3, "list");
  const asJson = parsed.booleans.has("json");
  if (action === "list") {
    args.expect(parsed, { flags: ["session"], positional: 1 });
    return await listQuestions(parsed.flags.session, asJson);
  }
  args.expect(parsed, {
    positional: 2,
    ...action === "answer" ? { flags: ["option", "text", "answers"] } : {}
  });
  const batchId = args.required(parsed, 1, "batchId");
  if (action === "show") {
    const batch = expectShape(
      await api(path`/questions/${batchId}`),
      "GET /questions/{id}",
      { id: is.string, questions: is.array, sessionTitle: is.string }
    );
    if (asJson) {
      out.json(batch);
      return;
    }
    printBatch(batch);
    return;
  }
  if (action === "answer") {
    const result2 = expectShape(
      await api(path`/questions/${batchId}/submit`, {
        method: "POST",
        body: { answers: collectAnswers(parsed) }
      }),
      "POST /questions/{id}/submit",
      { answeredCount: is.number }
    );
    if (asJson) {
      out.json(result2);
      return;
    }
    out.line(
      `${out.paint("\u2713", "green")} Answered ${result2.answeredCount} question(s); the session resumes.`
    );
    return;
  }
  const result = await api(
    path`/questions/${batchId}/cancel`,
    { method: "POST", body: {} }
  );
  if (asJson) {
    out.json(result);
    return;
  }
  out.line(
    'Round cancelled. The session stays parked; send a message to steer it: `hepha sessions send <id> "\u2026"`'
  );
}

// src/lib/follow/follow-constants.ts
var POLL_SECONDS = 3;
var PAGE_SIZE = 200;

// ../../packages/database/schema/advisor/constants.ts
var ADVISOR_NOTE_SEVERITY = {
  NOTE: "note",
  WARN: "warn",
  ALERT: "alert",
  SUGGESTION: "suggestion"
};
var ADVISOR_NOTE_STATUS = {
  POSTED: "posted",
  PENDING: "pending",
  CONFIRMED: "confirmed",
  DISMISSED: "dismissed"
};

// ../../packages/database/schema/advisor/advisor-note-was-delivered.ts
function advisorNoteWasDelivered(note) {
  return note.steered === true || note.severity === ADVISOR_NOTE_SEVERITY.WARN && note.status === ADVISOR_NOTE_STATUS.CONFIRMED;
}

// src/lib/truncate/truncate.ts
function truncate(text, max = 2e3) {
  const trimmed2 = out.safe(text).trim();
  return trimmed2.length > max ? `${trimmed2.slice(0, max)}\u2026` : trimmed2;
}

// src/lib/event/format-advisor-part.ts
function formatAdvisorPart(part) {
  const note = truncate(part.text, 400);
  const delivery = advisorNoteWasDelivered(part) ? "sent to the agent" : "not delivered";
  switch (part.severity) {
    case ADVISOR_NOTE_SEVERITY.ALERT:
      return out.paint(`advisor alert, ${delivery}: ${note}`, "red");
    case ADVISOR_NOTE_SEVERITY.WARN:
      return out.paint(`advisor warning, ${delivery}: ${note}`, "yellow");
    case ADVISOR_NOTE_SEVERITY.SUGGESTION: {
      const named = part.skillName ? ` ${part.skillName}` : "";
      const offer = delivery === "not delivered" ? delivery : "offered to the agent";
      return out.paint(
        `advisor skill suggestion${named}, ${offer}: ${note}`,
        "blue"
      );
    }
    case ADVISOR_NOTE_SEVERITY.NOTE:
      return out.paint(`advisor ${note}`, "dim");
    default:
      return part.severity;
  }
}

// ../../packages/realtime/src/events/deliverable/deliverable-download-path.ts
function deliverableDownloadPath(sessionId, pathname, options) {
  const query = new URLSearchParams({ pathname });
  if (options?.inline) {
    query.set("disposition", "inline");
  }
  return `/api/sessions/${sessionId}/deliverables?${query.toString()}`;
}

// ../../packages/realtime/src/events/attachment/format-attachment-size.ts
function formatAttachmentSize(bytes) {
  if (bytes < 1024) {
    return `${bytes} B`;
  }
  const kb = bytes / 1024;
  if (kb < 1024) {
    return `${kb.toFixed(0)} KB`;
  }
  return `${(kb / 1024).toFixed(1)} MB`;
}

// src/lib/event/format-deliverable-part.ts
function formatDeliverablePart(part, sessionId, baseUrl) {
  const entries = part.archive ? `, ${part.archive.entryCount} entries` : "";
  const size = `${formatAttachmentSize(part.sizeBytes)}${entries}`;
  const caption = part.caption ? ` - ${out.cell(part.caption)}` : "";
  const link = `${baseUrl}${deliverableDownloadPath(sessionId, part.pathname)}`;
  return `${out.paint("attach", "dim")} ${out.cell(part.name)} ${out.paint(`(${size})`, "dim")}${caption}
       ${out.cell(link)}`;
}

// ../../packages/agents/src/turns/turn-constants.ts
var SESSION_TERMINATION_MODES = {
  DESTROY: "destroy",
  DETACH: "detach",
  STOP: "stop",
  SUSPEND_TURN: "suspend-turn"
};
var ALL_SESSION_TERMINATION_MODES = Object.values(
  SESSION_TERMINATION_MODES
);
var HOST_TOOL_NAMES = {
  ADVISE: "advise",
  ASK_USER: "ask_user",
  SAVE_DEV_ENV_MANIFEST: "save_dev_env_manifest",
  REQUEST_DEV_ENV_CREDENTIALS: "request_dev_env_credentials",
  // Its sibling for every OTHER task type: the value is stored for the repo
  // and pushed to its Vercel project (docs/integrations/env-vars.md).
  REQUEST_ENV_VARS: "request_env_vars",
  GENERATE_DEV_ENV_SECRET: "generate_dev_env_secret",
  SET_DEV_ENV_SIZING: "set_dev_env_sizing",
  BAKE_DEV_ENV: "bake_dev_env",
  PROPOSE_PLAN: "propose_plan",
  REPORT_CONFIDENCE: "report_confidence",
  REQUEST_GUARDRAIL_APPROVAL: "request_guardrail_approval",
  // Somnium, mid-run: the automatic recall only fires on a session's FIRST
  // prompt, so the agent reads on demand and writes what it learns.
  MEMORY_SEARCH: "memory_search",
  MEMORY_WRITE: "memory_write",
  // Another chat, read through a separate model for one goal, never loaded
  // whole (docs/agents/session-reading.md).
  READ_SESSION: "read_session",
  // A project's shared context (docs/agents/projects.md): every session of the
  // project reads it in its instructions and writes back what it learned.
  PROJECT_CONTEXT_READ: "project_context_read",
  PROJECT_CONTEXT_WRITE: "project_context_write",
  START_IMPLEMENT_SESSION: "start_implement_session",
  LINEAR_LIST_ISSUES: "linear_list_issues",
  LINEAR_GET_ISSUE: "linear_get_issue",
  LINEAR_CREATE_ISSUE: "linear_create_issue",
  LINEAR_UPDATE_ISSUE: "linear_update_issue",
  LINEAR_COMMENT_ISSUE: "linear_comment_issue",
  SLACK_SEND_MESSAGE: "slack_send_message",
  REPORT_FINDING: "report_finding",
  LIST_FINDINGS: "list_findings",
  SKILL_SEARCH: "skill_search",
  SKILL_LOAD: "skill_load",
  SAVE_SKILL: "save_skill",
  RECORD_REVIEW_LESSON: "record_review_lesson",
  // Proposes an EDIT to a skill already in the registry: a version above the
  // skill's current one, invisible to sessions until a human approves it
  // (docs/08-skills/01-registry.md).
  PROPOSE_SKILL_EDIT: "propose_skill_edit",
  REQUEST_SKILL_ACTIVATION: "request_skill_activation",
  DELIVERABLE_PUBLISH: "deliverable_publish",
  // Design mode: pull a round of static bundles out of the sandbox, store them
  // as immutable versions, and hand back the public link the panel mirrors.
  PUBLISH_DESIGN: "publish_design",
  SET_LIVE_DESIGN: "set_live_design",
  LIST_AUTOMATIONS: "list_automations",
  GET_AUTOMATION: "get_automation",
  AUTOMATION_CATALOG: "automation_catalog",
  CREATE_AUTOMATION: "create_automation",
  UPDATE_AUTOMATION: "update_automation",
  DECK_LIST: "deck_list",
  DECK_CREATE: "deck_create",
  DECK_READ: "deck_read",
  DECK_WRITE_SLIDE: "deck_write_slide",
  DECK_MOVE_SLIDE: "deck_move_slide",
  DECK_DELETE_SLIDE: "deck_delete_slide",
  DECK_RENDER: "deck_render",
  MODEL3D_LIST: "model3d_list",
  MODEL3D_CREATE: "model3d_create",
  MODEL3D_READ: "model3d_read",
  MODEL3D_SOURCE_RESTORE: "model3d_source_restore",
  MODEL3D_PUBLISH: "model3d_publish",
  SHADER_LIST: "shader_list",
  SHADER_CREATE: "shader_create",
  SHADER_READ: "shader_read",
  SHADER_PUBLISH: "shader_publish",
  GPU_ATTACH: "gpu_attach",
  GPU_PUSH: "gpu_push",
  GPU_EXEC: "gpu_exec",
  GPU_PULL: "gpu_pull",
  GPU_STATUS: "gpu_status",
  GPU_DETACH: "gpu_detach",
  NOTEBOOK_OPEN: "notebook_open",
  NOTEBOOK_RUN: "notebook_run",
  NOTEBOOK_STATUS: "notebook_status",
  GPU_KERNEL_CONNECT: "gpu_kernel_connect",
  GPU_KERNEL_DISCONNECT: "gpu_kernel_disconnect",
  VIDEO_LIST: "video_list",
  VIDEO_CREATE: "video_create",
  VIDEO_READ: "video_read",
  VIDEO_SOURCE_RESTORE: "video_source_restore",
  VIDEO_PUBLISH: "video_publish",
  GENERATE_VOICE: "generate_voice",
  GENERATE_MUSIC: "generate_music",
  GENERATE_SOUND_EFFECT: "generate_sound_effect",
  LIST_VOICES: "list_voices",
  VIDEO_SET_VOICES: "video_set_voices",
  SUBMIT_SPECS: "submit_specs",
  SET_BRAND_KIT: "set_brand_kit",
  PLAN_TICKETS: "plan_tickets",
  PLAN_FOLLOW_UPS: "plan_follow_ups",
  // Run engine seats (docs/agents/software-factory.md): the foundation session
  // reports its checklist, the merge-decision session settles a ticket's PR,
  // the QA session settles the verification phase, a ticket seat reads the specs.
  MARK_PROVISIONING_STEP: "mark_provisioning_step",
  DECIDE_MERGE: "decide_merge",
  DECIDE_QA: "decide_qa",
  READ_FACTORY_SPECS: "read_factory_specs",
  REPORT_RELEASE_QA_FLOWS: "report_release_qa_flows",
  REPORT_RELEASE_QA_RESULTS: "report_release_qa_results",
  REPORT_QA_FLOW_RESULT: "report_qa_flow_result",
  DECIDE_RELEASE_QA: "decide_release_qa",
  // Advisory dynamic playbook (WS6): the agent defines its own ordered steps
  // (set_steps) and fills each step's todo checklist (set_todos). Replaces the
  // fixed 5-phase set_phase pointer; neither parks the turn, the runner park
  // logic is untouched. A step's status is derived from its todos.
  SET_STEPS: "set_steps",
  SET_TODOS: "set_todos",
  // The chat's title is generated from the FIRST prompt by the platform's
  // small model. This lets the agent correct it mid-run when the real scope
  // turns out to be something else, and only then (see the pipeline
  // templates). Refused once a human has renamed the chat; never parks.
  RENAME_SESSION: "rename_session",
  // The Implement evidence gate reads it to decide whether a recording is owed.
  DECLARE_USER_FLOW: "declare_user_flow",
  // Files an issue in Hephaistos' own Linear tracker; never parks the turn.
  REPORT_HARNESS_ISSUE: "report_harness_issue",
  REPORT_DEV_SERVER: "report_dev_server",
  RUN_VALIDATION: "run_validation",
  COLLECT_REVIEW_SNAPSHOT: "collect_review_snapshot",
  READ_REVIEW_EVIDENCE_SOURCE: "read_review_evidence_source",
  FIND_REVIEW_EXAMPLES: "find_review_examples",
  SUBMIT_REVIEW_EVIDENCE: "submit_review_evidence",
  VISUAL_TESTS_RUN: "visual_tests_run",
  VISUAL_TESTS_STATUS: "visual_tests_status",
  REVIEW_SCOPE: "review_scope",
  FILTER_REVIEW_FINDINGS: "filter_review_findings",
  SUBMIT_REVIEW_SUMMARY: "submit_review_summary",
  // Media tool (Stage 1): generate images host-side via the AI Gateway; the
  // result renders inline in the thread. Always available, never parks the
  // turn.
  GENERATE_IMAGE: "generate_image",
  // In-sandbox annotation (#43): renders the source image + an SVG overlay in
  // the sandbox's chromium and screenshots it. NEVER host-side, the previous
  // @napi-rs/canvas implementation broke the glibc Trigger.dev worker deploy.
  ANNOTATE_IMAGE: "annotate_image",
  // WS8: the admin-configured image_generator registry, the agent reads the
  // enabled {model, description} rows to pick a model for generate_image.
  GET_IMAGE_GENERATORS: "get_image_generators",
  // Capture tools (Stage 2): drive a headless chromium INSIDE the session's
  // sandbox to screenshot / record a running app URL (or a file:// the agent
  // rendered), then attach the result inline. Provision the sandbox on demand
  // (WS7), always exposed.
  TAKE_SCREENSHOT: "take_screenshot",
  RECORD_VIDEO: "record_video",
  // Interactive browser control: ONE persistent chromium per sandbox, one
  // context per session, driven by the in-sandbox daemon (packages/tasks
  // lib/browser). The snapshot is an accessibility tree with `ref=eN` handles
  // the act tool targets, no coordinates, no throwaway playwright script.
  BROWSER_NAVIGATE: "browser_navigate",
  BROWSER_SNAPSHOT: "browser_snapshot",
  BROWSER_ACT: "browser_act",
  // The same page loop, decided by Jev instead of an agent turn: one call
  // walks a whole flow (docs/agents/browser-control.md).
  BROWSER_AUTOPILOT: "browser_autopilot",
  BROWSER_WAIT: "browser_wait",
  BROWSER_INSPECT: "browser_inspect",
  BROWSER_SCREENSHOT: "browser_screenshot",
  // A recording is a PAIR: playwright only films a context it created with
  // recording on, so start recycles the context and stop hands back the clip.
  BROWSER_RECORD_START: "browser_record_start",
  BROWSER_RECORD_STOP: "browser_record_stop",
  BROWSER_CLOSE: "browser_close",
  // The escape hatch: JS evaluated in a Node context that SURVIVES between
  // calls, with the live page bound into it.
  REPL_EVAL: "repl_eval",
  REPL_RESET: "repl_reset",
  // WS5: upload a PNG the agent produced in the sandbox (script/python/write)
  // as a media reference, silent for ask_user option images, inline otherwise.
  ATTACH_IMAGE: "attach_image",
  // Browser-session vault: sites a human captured with the Hephaistos
  // extension, applied to the live browser one domain at a time, on demand.
  BROWSER_SESSIONS_LIST: "browser_sessions_list",
  BROWSER_USE_SESSION: "browser_use_session",
  BROWSER_FORGET_SESSION: "browser_forget_session",
  // Outbound files: any format the human should keep, uploaded from the sandbox
  // to durable storage and downloadable once the sandbox is gone.
  ATTACH_FILE: "attach_file",
  SHOW_INTERACTIVE_VIEW: "show_interactive_view",
  PREVIEW_INTERACTIVE_VIEW: "preview_interactive_view",
  // Delegation: a task is the work, a session is one attempt at it. The agent
  // describes the task and the PLATFORM dispatches it; these address the task,
  // never a runner. Fire-and-forget, none of them park the turn.
  CREATE_TASK: "create_task",
  LIST_TASKS: "list_tasks",
  UPDATE_TASK: "update_task",
  PEEK_TASK: "peek_task",
  SEND_TO_TASK: "send_to_task",
  STOP_TASK: "stop_task",
  PICKUP_TASK: "pickup_task",
  // A JS script whose agent() calls ARE platform tasks (docs/agents/workflows.md).
  WORKFLOW: "workflow",
  // docs/integrations/database-access.md; detect is environment-setup only.
  DATABASE_QUERY: "database_query",
  READ_VERCEL_DEPLOYMENT: "read_vercel_deployment",
  DATABASE_WRITE: "database_write",
  DETECT_PROJECT_DATABASE: "detect_project_database",
  BIND_PROJECT_DATABASE: "bind_project_database",
  REQUEST_DATABASE_URL: "request_database_url",
  // docs/integrations/provisioning.md
  PROVISION_SERVICE: "provision_service",
  // Historical: the agent-addressed family the tools above replaced. No agent
  // can call them, but old transcripts still render rows named after them.
  SPAWN_AGENT: "spawn_agent",
  LIST_AGENTS: "list_agents",
  PEEK_AGENT: "peek_agent",
  SEND_TO_AGENT: "send_to_agent",
  STOP_AGENT: "stop_agent"
};
var ALL_HOST_TOOL_NAMES = Object.values(HOST_TOOL_NAMES);
var TURN_CLOSING_TOOL_NAMES = [
  HOST_TOOL_NAMES.UPDATE_TASK,
  HOST_TOOL_NAMES.CREATE_TASK
];
var WORKSPACE_TOOL_NAMES = {
  BASH: "workspace_bash",
  READ: "workspace_read",
  WRITE: "workspace_write",
  EDIT: "workspace_edit",
  GLOB: "workspace_glob",
  GREP: "workspace_grep",
  TASK_OUTPUT: "workspace_task_output",
  TASK_STOP: "workspace_task_stop"
};
var AGENT_MESSAGE_DELIVERY_MODES = {
  INTERRUPT: "interrupt",
  AFTER_TOOL: "after_tool",
  QUEUE: "queue"
};
var ALL_AGENT_MESSAGE_DELIVERY_MODES = Object.values(
  AGENT_MESSAGE_DELIVERY_MODES
);
var DEFAULT_AGENT_MESSAGE_DELIVERY_MODE = AGENT_MESSAGE_DELIVERY_MODES.AFTER_TOOL;

// ../../packages/agents/src/tool-activity/canonical/canonical-tool-name-constants.ts
var NATIVE_EQUIVALENT = {
  [WORKSPACE_TOOL_NAMES.BASH]: "bash",
  [WORKSPACE_TOOL_NAMES.READ]: "read",
  [WORKSPACE_TOOL_NAMES.WRITE]: "write",
  [WORKSPACE_TOOL_NAMES.EDIT]: "edit",
  [WORKSPACE_TOOL_NAMES.GLOB]: "glob",
  [WORKSPACE_TOOL_NAMES.GREP]: "grep"
};

// ../../packages/agents/src/tool-activity/tool-input/is-record.ts
function isRecord(value) {
  return typeof value === "object" && value !== null && !Array.isArray(value);
}

// ../../packages/agents/src/tool-activity/tool-input/str.ts
function str(value) {
  return typeof value === "string" && value.length > 0 ? value : void 0;
}

// ../../packages/agents/src/tool-activity/tool-input/trimmed.ts
function trimmed(value) {
  const text = str(value)?.trim();
  return text ? text : null;
}

// ../../packages/agents/src/tool-activity/tool-input/truncate.ts
function truncate2(value, max) {
  const text = value.trim();
  return text.length > max ? `${text.slice(0, max - 1)}\u2026` : text;
}

// ../../packages/agents/src/tool-activity/tool-input/verbatim.ts
function verbatim(value) {
  const text = str(value);
  return text && text.trim().length > 0 ? text : null;
}

// ../../packages/agents/src/tool-activity/tool-input.ts
var toolInput = { isRecord, str, trimmed, truncate: truncate2, verbatim };

// ../../packages/agents/src/tool-activity/label-only.ts
function labelOnly(text) {
  return { text, command: null };
}

// ../../packages/agents/src/tool-activity/tool-activity-basename.ts
function toolActivityBasename(path2) {
  if (!path2) {
    return void 0;
  }
  const segments2 = path2.split(/[/\\]/).filter(Boolean);
  return segments2.at(-1) ?? path2;
}

// ../../packages/agents/src/tool-activity/tool-activity-hostname.ts
function toolActivityHostname(url) {
  if (!url) {
    return void 0;
  }
  try {
    return new URL(url).hostname;
  } catch {
    return url.replace(/^https?:\/\//, "").split("/")[0] || void 0;
  }
}

// ../../packages/agents/src/tool-activity/capture/capture-label.ts
function captureLabel(labels, state, options) {
  if (options.isOption) {
    if (state.hasFailed) {
      return labelOnly(labels.optionFailed);
    }
    return labelOnly(
      state.isRunning ? labels.optionRunning : labels.optionDone
    );
  }
  if (state.hasFailed) {
    return labelOnly(labels.failed(options.subject));
  }
  return labelOnly(
    state.isRunning ? labels.running(options.subject) : labels.done(options.subject)
  );
}

// ../../packages/agents/src/tool-activity/capture/describe-annotate-image.ts
function describeAnnotateImage(record, state) {
  return captureLabel(
    {
      optionFailed: "Failed to annotate a silent image",
      optionRunning: "Annotating a silent image",
      optionDone: "Annotated a silent image",
      failed: () => "Failed to annotate an image",
      running: () => "Annotating an image",
      done: () => "Annotated an image"
    },
    state,
    { isOption: record.silent === true, subject: null }
  );
}

// ../../packages/core/src/source-scan/end-of-comment.ts
function endOfComment(source, at) {
  if (source[at] !== "/") return at;
  if (source[at + 1] === "/") {
    const line = source.indexOf("\n", at);
    return line === -1 ? source.length : line - 1;
  }
  if (source[at + 1] === "*") {
    const block = source.indexOf("*/", at + 2);
    return block === -1 ? source.length : block + 1;
  }
  return at;
}

// ../../packages/core/src/source-scan/end-of-string-literal.ts
function endOfStringLiteral(source, open) {
  const quote = source[open];
  for (let at = open + 1; at < source.length; at += 1) {
    if (source[at] === "\\") at += 1;
    else if (source[at] === quote) return at;
  }
  return source.length;
}

// ../../packages/core/src/source-scan/end-of-skippable.ts
function endOfSkippable(source, at) {
  const char = source[at];
  if (char === "'" || char === '"' || char === "`") {
    return endOfStringLiteral(source, at);
  }
  return endOfComment(source, at);
}

// ../../packages/core/src/source-scan/end-of-object-literal.ts
function endOfObjectLiteral(source, open) {
  let depth = 0;
  for (let at = open; at < source.length; at += 1) {
    const skipped = endOfSkippable(source, at);
    if (skipped !== at) {
      at = skipped;
      continue;
    }
    if (source[at] === "{") depth += 1;
    else if (source[at] === "}" && (depth -= 1) === 0) return at + 1;
  }
  return -1;
}

// ../../packages/core/src/source-scan/has-spread-in-code.ts
function hasSpreadInCode(literal) {
  for (let at = 0; at < literal.length; at += 1) {
    const skipped = endOfSkippable(literal, at);
    if (skipped !== at) {
      at = skipped;
      continue;
    }
    if (literal.startsWith("...", at)) return true;
  }
  return false;
}

// ../../packages/core/src/source-scan/meta-declaration-open.ts
function metaDeclarationOpen(script) {
  const anchor = /export[ \t]+const[ \t]+meta[ \t]*=\s*\{/y;
  let at = 0;
  while (at < script.length) {
    if (/\s/.test(script[at] ?? "")) {
      at += 1;
      continue;
    }
    const comment = endOfComment(script, at);
    if (comment !== at) {
      at = comment + 1;
      continue;
    }
    anchor.lastIndex = at;
    return anchor.test(script) ? script.indexOf("{", at) : -1;
  }
  return -1;
}

// ../../packages/core/src/source-scan/unescape-string-literal.ts
function unescapeStringLiteral(body) {
  const single = {
    n: "\n",
    t: "	",
    r: "\r",
    b: "\b",
    f: "\f",
    v: "\v",
    "0": "\0"
  };
  return body.replace(
    /\\(u\{([0-9a-fA-F]+)\}|u([0-9a-fA-F]{4})|x([0-9a-fA-F]{2})|.)/gs,
    (_, escape, braced, unicode, hex) => {
      const code = braced ?? unicode ?? hex;
      if (code === void 0) return single[escape] ?? escape;
      const point = Number.parseInt(code, 16);
      return point <= 1114111 ? String.fromCodePoint(point) : `\\${escape}`;
    }
  );
}

// ../../packages/core/src/source-scan/quoted-field-at.ts
function quotedFieldAt(literal, at, field) {
  const key = /^(['"])?/.exec(literal.slice(at));
  const quoted = key?.[1] ?? "";
  const start = at + quoted.length;
  if (!literal.startsWith(field, start)) return void 0;
  if (!quoted && /[A-Za-z0-9_$]/.test(literal[at - 1] ?? "")) return void 0;
  const after = literal.slice(start + field.length);
  const opener = new RegExp(`^${quoted}\\s*:\\s*['"\`]`).exec(after);
  if (!opener) return void 0;
  const quote = start + field.length + opener[0].length - 1;
  const close = endOfStringLiteral(literal, quote);
  let tail = close + 1;
  for (; ; ) {
    while (/\s/.test(literal[tail] ?? "")) tail += 1;
    const skipped = endOfSkippable(literal, tail);
    if (skipped === tail) break;
    tail = skipped + 1;
  }
  if (literal[tail] !== "," && literal[tail] !== "}") return void 0;
  if (literal[quote] === "`" && /\$\{/.test(literal.slice(quote, close))) {
    return void 0;
  }
  return unescapeStringLiteral(literal.slice(quote + 1, close));
}

// ../../packages/core/src/source-scan/top-level-string-field.ts
function topLevelStringField(literal, field) {
  let depth = 0;
  let found;
  for (let at = 0; at < literal.length; at += 1) {
    if (depth === 1) {
      const value = quotedFieldAt(literal, at, field);
      if (value !== void 0) found = value;
    }
    const skipped = endOfSkippable(literal, at);
    if (skipped !== at) {
      at = skipped;
      continue;
    }
    const char = literal[at];
    if (char === "{" || char === "[") depth += 1;
    else if (char === "}" || char === "]") depth -= 1;
  }
  return found;
}

// ../../packages/core/src/source-scan/meta-name-in-script.ts
function metaNameInScript(script) {
  const open = metaDeclarationOpen(script);
  if (open === -1) return void 0;
  const end = endOfObjectLiteral(script, open);
  if (end === -1) return void 0;
  const literal = script.slice(open, end);
  if (hasSpreadInCode(literal)) return void 0;
  const name = topLevelStringField(literal, "name");
  return name?.trim() && topLevelStringField(literal, "description")?.trim() ? name.slice(0, 80) : void 0;
}

// ../../packages/agents/src/tool-activity/canonical/workflow-script-name.ts
function workflowScriptName(script) {
  return metaNameInScript(script);
}

// ../../packages/agents/src/tool-activity/canonical/describe-workflow.ts
function describeWorkflow(record) {
  const name = workflowScriptName(toolInput.str(record.script) ?? "");
  return labelOnly(name ? `Ran workflow "${name}"` : "Ran a workflow");
}

// ../../packages/agents/src/tool-activity/capture/capture-label-constants.ts
var CAPTURE_LABEL_MAX = 60;

// ../../packages/agents/src/tool-activity/capture/describe-attach-image.ts
function describeAttachImage(record, state) {
  const file = toolActivityBasename(toolInput.str(record.path));
  return captureLabel(
    {
      optionFailed: "Failed to attach a silent image",
      optionRunning: "Attaching a silent image",
      optionDone: "Attached a silent image",
      failed: (subject) => subject ? `Failed to attach ${subject}` : "Failed to attach an image",
      running: (subject) => subject ? `Attaching ${subject}` : "Attaching an image",
      done: (subject) => subject ? `Attached ${subject}` : "Attached an image"
    },
    state,
    {
      isOption: record.silent === true,
      subject: file ? toolInput.truncate(file, CAPTURE_LABEL_MAX) : null
    }
  );
}

// ../../packages/agents/src/tool-activity/capture/describe-generate-image.ts
function describeGenerateImage(_record, state) {
  return captureLabel(
    {
      optionFailed: "Failed to generate an image",
      optionRunning: "Generating an image",
      optionDone: "Generated an image",
      failed: () => "Failed to generate an image",
      running: () => "Generating an image",
      done: () => "Generated an image"
    },
    state,
    { isOption: false, subject: null }
  );
}

// ../../packages/agents/src/tool-activity/capture/capture-target.ts
function captureTarget(url) {
  if (!url) {
    return void 0;
  }
  try {
    const parsed = new URL(url);
    if (parsed.protocol === "file:") {
      return toolActivityBasename(parsed.pathname) ?? url;
    }
    return parsed.host || parsed.hostname;
  } catch {
    return toolActivityBasename(url) ?? url;
  }
}

// ../../packages/agents/src/tool-activity/capture/describe-record-video.ts
function describeRecordVideo(record, state) {
  const target = captureTarget(toolInput.str(record.url));
  return captureLabel(
    {
      // record_video has no option variant: a strip thumb is a still image.
      optionFailed: "Failed to record a video",
      optionRunning: "Recording a video",
      optionDone: "Recorded a video",
      failed: (subject) => subject ? `Failed to record a video of ${subject}` : "Failed to record a video",
      running: (subject) => subject ? `Recording a video of ${subject}` : "Recording a video",
      done: (subject) => subject ? `Recorded a video of ${subject}` : "Recorded a video"
    },
    state,
    {
      isOption: false,
      subject: target ? toolInput.truncate(target, CAPTURE_LABEL_MAX) : null
    }
  );
}

// ../../packages/agents/src/tool-activity/capture/describe-take-screenshot.ts
function describeTakeScreenshot(record, state) {
  const target = captureTarget(toolInput.str(record.url));
  return captureLabel(
    {
      optionFailed: "Failed to take a silent screenshot",
      optionRunning: "Taking a silent screenshot",
      optionDone: "Took a silent screenshot",
      failed: (subject) => subject ? `Failed to screenshot ${subject}` : "Failed to take a screenshot",
      running: (subject) => subject ? `Taking a screenshot of ${subject}` : "Taking a screenshot",
      done: (subject) => subject ? `Took a screenshot of ${subject}` : "Took a screenshot"
    },
    state,
    {
      isOption: record.silent === true,
      subject: target ? toolInput.truncate(target, CAPTURE_LABEL_MAX) : null
    }
  );
}

// ../../packages/agents/src/tool-activity/canonical/describe-memory-search.ts
function describeMemorySearch(record) {
  const query = toolInput.str(record.query);
  return labelOnly(
    query ? `Searched its memory: ${toolInput.truncate(query, 60)}` : "Searched its memory"
  );
}

// ../../packages/agents/src/tool-activity/canonical/describe-report-finding.ts
function describeReportFinding(record) {
  const title = toolInput.str(record.title);
  const severity = toolInput.str(record.severity);
  if (!title) {
    return labelOnly("Reported a finding");
  }
  const prefix = severity ? `${severity}: ` : "";
  return labelOnly(
    `Reported a finding, ${prefix}${toolInput.truncate(title, 60)}`
  );
}

// ../../packages/agents/src/tool-activity/canonical/describe-report-dev-server.ts
function describeReportDevServer(record) {
  const status = toolInput.str(record.status);
  const port = toolInput.str(record.port) ?? String(record.port ?? "");
  if (status && port) {
    return labelOnly(`Reported the dev server ${status} on port ${port}`);
  }
  if (status) {
    return labelOnly(`Reported the dev server ${status}`);
  }
  return labelOnly("Reported the dev server");
}

// ../../packages/agents/src/tool-activity/canonical/describe-request-env-vars.ts
function describeRequestEnvVars(record) {
  const asked = Array.isArray(record.variables) ? record.variables.length : null;
  return labelOnly(
    asked ? `Requested ${asked} environment variable${asked === 1 ? "" : "s"}` : "Requested environment variables"
  );
}

// ../../packages/agents/src/tool-activity/deliverable/describe-attach-file.ts
function describeAttachFile(record, state) {
  const file = toolActivityBasename(toolInput.str(record.path));
  const subject = file ?? "a file";
  if (state.hasFailed) {
    return labelOnly(`Failed to deliver ${subject}`);
  }
  return labelOnly(
    state.isRunning ? `Delivering ${subject}` : `Delivered ${subject}`
  );
}

// ../../packages/agents/src/tool-activity/automation/linear-issue-label.ts
function linearIssueLabel(verb, issue) {
  const id = toolInput.trimmed(issue);
  return labelOnly(id ? `${verb} ${id}` : verb);
}

// ../../packages/agents/src/tool-activity/automation/automation-tool-activity-describers.ts
var automationToolActivityDescribers = [
  [HOST_TOOL_NAMES.LIST_AUTOMATIONS, () => labelOnly("Listed automations")],
  [HOST_TOOL_NAMES.GET_AUTOMATION, () => labelOnly("Read an automation")],
  [
    HOST_TOOL_NAMES.START_IMPLEMENT_SESSION,
    () => labelOnly("Started an Implement session")
  ],
  [
    HOST_TOOL_NAMES.AUTOMATION_CATALOG,
    () => labelOnly("Read the automation catalog")
  ],
  [
    HOST_TOOL_NAMES.CREATE_AUTOMATION,
    (record) => {
      const name = toolInput.trimmed(record.name);
      return labelOnly(
        name ? `Created automation ${name}` : "Created an automation"
      );
    }
  ],
  [
    HOST_TOOL_NAMES.UPDATE_AUTOMATION,
    (record) => {
      const name = toolInput.trimmed(record.name);
      return labelOnly(
        name ? `Updated automation ${name}` : "Updated an automation"
      );
    }
  ],
  [HOST_TOOL_NAMES.LINEAR_LIST_ISSUES, () => labelOnly("Listed Linear issues")],
  [
    HOST_TOOL_NAMES.LINEAR_GET_ISSUE,
    (record) => linearIssueLabel("Read Linear issue", record.issue)
  ],
  [
    HOST_TOOL_NAMES.LINEAR_CREATE_ISSUE,
    () => labelOnly("Created a Linear issue")
  ],
  [
    HOST_TOOL_NAMES.LINEAR_UPDATE_ISSUE,
    (record) => linearIssueLabel("Updated Linear issue", record.issue)
  ],
  [
    HOST_TOOL_NAMES.LINEAR_COMMENT_ISSUE,
    (record) => linearIssueLabel("Commented on Linear issue", record.issue)
  ]
];

// ../../packages/agents/src/tool-activity/state-label.ts
function stateLabel(state, labels) {
  if (state.hasFailed) return labelOnly(labels.failed);
  return labelOnly(state.isRunning ? labels.running : labels.done);
}

// ../../packages/agents/src/tool-activity/browser/browser-activity-constants.ts
var BROWSER_ACT_VERBS = Object.assign(/* @__PURE__ */ Object.create(null), {
  click: { done: "Clicked", gerund: "Clicking", bare: "click" },
  dblclick: {
    done: "Double-clicked",
    gerund: "Double-clicking",
    bare: "double-click"
  },
  fill: { done: "Filled", gerund: "Filling", bare: "fill" },
  type: { done: "Typed into", gerund: "Typing into", bare: "type into" },
  select: { done: "Selected in", gerund: "Selecting in", bare: "select in" },
  check: { done: "Checked", gerund: "Checking", bare: "check" },
  uncheck: { done: "Unchecked", gerund: "Unchecking", bare: "uncheck" },
  hover: { done: "Hovered", gerund: "Hovering", bare: "hover" },
  press: {
    done: "Pressed a key on",
    gerund: "Pressing a key on",
    bare: "press a key on"
  },
  scroll: { done: "Scrolled", gerund: "Scrolling", bare: "scroll" },
  upload: {
    done: "Uploaded a file to",
    gerund: "Uploading a file to",
    bare: "upload a file to"
  }
});
var BROWSER_HISTORY_LABELS = Object.assign(/* @__PURE__ */ Object.create(null), {
  back: { done: "Went back", gerund: "Going back", bare: "go back" },
  forward: {
    done: "Went forward",
    gerund: "Going forward",
    bare: "go forward"
  },
  reload: {
    done: "Reloaded the page",
    gerund: "Reloading the page",
    bare: "reload the page"
  }
});

// ../../packages/agents/src/tool-activity/browser/describe-browser-act.ts
function describeBrowserAct(record, state) {
  const action = toolInput.str(record.action) ?? "click";
  const verb = (Object.hasOwn(BROWSER_ACT_VERBS, action) ? BROWSER_ACT_VERBS[action] : void 0) ?? {
    done: "Acted on",
    gerund: "Acting on",
    bare: "act on"
  };
  const target = toolInput.trimmed(record.name) ?? toolInput.trimmed(record.selector) ?? toolInput.trimmed(record.ref) ?? toolInput.trimmed(record.role);
  const subject = target ? toolInput.truncate(target, 60) : "an element";
  return stateLabel(state, {
    failed: `Could not ${verb.bare} ${subject}`,
    running: `${verb.gerund} ${subject}`,
    done: `${verb.done} ${subject}`
  });
}

// ../../packages/agents/src/tool-activity/browser/describe-browser-navigate.ts
function describeBrowserNavigate(record, state) {
  const action = toolInput.str(record.action);
  if (action && action !== "goto") {
    const move = Object.hasOwn(BROWSER_HISTORY_LABELS, action) ? BROWSER_HISTORY_LABELS[action] : void 0;
    return move ? stateLabel(state, {
      failed: `Could not ${move.bare}`,
      running: move.gerund,
      done: move.done
    }) : stateLabel(state, {
      failed: "Could not move in the browser",
      running: "Moving in the browser",
      done: "Moved in the browser"
    });
  }
  const host = toolActivityHostname(toolInput.str(record.url));
  const subject = host ?? "a page";
  return stateLabel(state, {
    failed: `Could not open ${subject}`,
    running: `Opening ${subject}`,
    done: `Opened ${subject}`
  });
}

// ../../packages/agents/src/tool-activity/browser/browser-tool-activity-describers.ts
var browserToolActivityDescribers = [
  [HOST_TOOL_NAMES.BROWSER_NAVIGATE, describeBrowserNavigate],
  [HOST_TOOL_NAMES.BROWSER_ACT, describeBrowserAct],
  [
    // The goal is the only thing worth reading: the steps are in the result.
    HOST_TOOL_NAMES.BROWSER_AUTOPILOT,
    (record, state) => {
      const goal = toolInput.str(record.goal);
      return stateLabel(state, {
        failed: goal ? `Could not: ${goal}` : "Could not drive the browser",
        running: goal ? `Driving the browser: ${goal}` : "Driving the browser",
        done: goal ? `Drove the browser: ${goal}` : "Drove the browser"
      });
    }
  ],
  [
    HOST_TOOL_NAMES.BROWSER_SNAPSHOT,
    (_record, state) => stateLabel(state, {
      failed: "Could not read the page",
      running: "Reading the page",
      done: "Read the page"
    })
  ],
  [
    HOST_TOOL_NAMES.BROWSER_WAIT,
    (_record, state) => stateLabel(state, {
      failed: "Gave up waiting for the page",
      running: "Waiting for the page",
      done: "Waited for the page"
    })
  ],
  [
    HOST_TOOL_NAMES.BROWSER_INSPECT,
    (_record, state) => stateLabel(state, {
      failed: "Could not read the console and network",
      running: "Reading the console and network",
      done: "Read the console and network"
    })
  ],
  [
    HOST_TOOL_NAMES.BROWSER_SCREENSHOT,
    (record, state) => describeTakeScreenshot(record, state)
  ],
  [
    HOST_TOOL_NAMES.BROWSER_RECORD_START,
    (_record, state) => stateLabel(state, {
      failed: "Could not start recording",
      running: "Starting a recording",
      done: "Started recording the browser"
    })
  ],
  [
    HOST_TOOL_NAMES.BROWSER_RECORD_STOP,
    (_record, state) => stateLabel(state, {
      failed: "Could not finish the recording",
      running: "Finishing the recording",
      done: "Recorded the browser"
    })
  ],
  [
    HOST_TOOL_NAMES.BROWSER_CLOSE,
    (_record, state) => stateLabel(state, {
      failed: "Could not close the browser",
      running: "Closing the browser",
      done: "Closed the browser"
    })
  ],
  [
    HOST_TOOL_NAMES.BROWSER_SESSIONS_LIST,
    (_record, state) => stateLabel(state, {
      failed: "Could not read your captured logins",
      running: "Listing your captured logins",
      done: "Listed your captured logins"
    })
  ],
  [
    // The site, never what the vault holds for it.
    HOST_TOOL_NAMES.BROWSER_USE_SESSION,
    (record, state) => {
      const domain = toolInput.str(record.domain);
      return stateLabel(state, {
        failed: domain ? `Could not sign in to ${domain} as you` : "Could not load a captured login",
        running: domain ? `Signing in to ${domain} as you` : "Loading a captured login",
        done: domain ? `Signed in to ${domain} as you` : "Loaded a captured login"
      });
    }
  ],
  [
    HOST_TOOL_NAMES.BROWSER_FORGET_SESSION,
    (_record, state) => stateLabel(state, {
      failed: "Could not forget the captured logins",
      running: "Forgetting the captured logins",
      done: "Forgot the captured logins"
    })
  ],
  [
    HOST_TOOL_NAMES.REPL_EVAL,
    (_record, state) => stateLabel(state, {
      failed: "JavaScript in the REPL failed",
      running: "Running JavaScript in the REPL",
      done: "Ran JavaScript in the REPL"
    })
  ],
  [
    HOST_TOOL_NAMES.REPL_RESET,
    (_record, state) => stateLabel(state, {
      failed: "Could not reset the REPL",
      running: "Resetting the REPL",
      done: "Reset the REPL"
    })
  ]
];

// ../../packages/agents/src/tool-activity/canonical/tool-execute/mcp-prefix-constants.ts
var MCP_ID_PREFIX = "mcp/";
var MCP_ENVELOPE_PAIR = /,?"([^"\\]*)":("(?:[^"\\]|\\["\\/bfnrt]|\\u[0-9a-fA-F]{4})*"|true|false|null|-?\d+(?:\.\d+)?)/y;
var MCP_ENVELOPE_KEYS = ["server", "reason"];

// ../../packages/agents/src/tool-activity/canonical/tool-execute/parse-tool-output.ts
function parseToolOutput(output) {
  if (toolInput.isRecord(output)) return output;
  if (typeof output !== "string") return null;
  const envelope = {};
  if (!output.startsWith("{")) return envelope;
  MCP_ENVELOPE_PAIR.lastIndex = 1;
  let pair = MCP_ENVELOPE_PAIR.exec(output);
  while (pair) {
    const [, key, jsonValue] = pair;
    const isWantedKey = key !== void 0 && MCP_ENVELOPE_KEYS.includes(key);
    if (isWantedKey && jsonValue !== void 0 && !(key in envelope)) {
      envelope[key] = JSON.parse(jsonValue);
    }
    pair = MCP_ENVELOPE_PAIR.exec(output);
  }
  return envelope;
}

// ../../packages/agents/src/tool-activity/canonical/tool-execute/describe-tool-execute.ts
function describeToolExecute(record, output) {
  const target = toolInput.str(record.tool);
  if (!target) return labelOnly("Ran an integration tool");
  if (!target.startsWith(MCP_ID_PREFIX)) {
    return labelOnly(`Ran ${toolInput.truncate(target, 60)}`);
  }
  const rest = target.slice(MCP_ID_PREFIX.length);
  const tool = rest.slice(rest.indexOf("/") + 1);
  const result = parseToolOutput(output);
  const server = toolInput.str(result?.server);
  const name = toolInput.truncate(tool, 40);
  const label = server ? `${name} \xB7 ${toolInput.truncate(server, 20)}` : name;
  if (result?.reason === "mcp_disconnected") {
    return labelOnly(`${label} failed: MCP disconnected, sign in again`);
  }
  return labelOnly(`Ran ${label}`);
}

// ../../packages/agents/src/tool-activity/canonical/integration-tool-activity-describers.ts
var integrationToolActivityDescribers = [
  [
    HOST_TOOL_NAMES.READ_VERCEL_DEPLOYMENT,
    (record) => labelOnly(
      `Read Vercel deployment for ${toolInput.str(record.sha)?.slice(0, 7) ?? "a commit"}`
    )
  ],
  [
    HOST_TOOL_NAMES.PROVISION_SERVICE,
    (record) => {
      const service = toolInput.str(record.service);
      return labelOnly(
        service ? `Requested approval to provision ${service}` : "Requested approval to provision a service"
      );
    }
  ],
  [
    "tool_search",
    (record) => {
      const query = toolInput.str(record.query);
      return labelOnly(
        query ? `Searched integrations for "${toolInput.truncate(query, 60)}"` : "Searched the connected integrations"
      );
    }
  ],
  [
    "tool_execute",
    (record, state) => describeToolExecute(record, state.output)
  ]
];

// ../../packages/agents/src/tool-activity/database/database-tool-activity-describers.ts
var databaseToolActivityDescribers = [
  [
    HOST_TOOL_NAMES.DETECT_PROJECT_DATABASE,
    () => labelOnly("Recorded the project database")
  ],
  [
    HOST_TOOL_NAMES.BIND_PROJECT_DATABASE,
    () => labelOnly("Requested approval to bind the project database")
  ],
  [
    HOST_TOOL_NAMES.REQUEST_DATABASE_URL,
    () => labelOnly("Asked for the production database URL")
  ],
  [
    HOST_TOOL_NAMES.DATABASE_QUERY,
    (record) => {
      const branch = toolInput.trimmed(record.branch) ?? "production";
      const sql = toolInput.verbatim(record.sql);
      return sql ? { text: `Read the database (${branch})`, command: sql } : labelOnly(`Read the database (${branch})`);
    }
  ],
  [
    HOST_TOOL_NAMES.DATABASE_WRITE,
    (record) => {
      const branch = toolInput.trimmed(record.branch) ?? "production";
      const sql = toolInput.verbatim(record.sql);
      return sql ? {
        text: `Requested approval to write to the database (${branch})`,
        command: sql
      } : labelOnly(`Requested approval to write to the database (${branch})`);
    }
  ]
];

// ../../packages/agents/src/tool-activity/design/design-tool-activity-describers.ts
var designToolActivityDescribers = [
  [
    HOST_TOOL_NAMES.PUBLISH_DESIGN,
    (record) => {
      const count = Array.isArray(record.directions) ? record.directions.length : 0;
      return labelOnly(
        count > 1 ? `Published ${count} design directions` : "Published a design"
      );
    }
  ],
  [
    HOST_TOOL_NAMES.SET_LIVE_DESIGN,
    (record) => {
      const version = toolInput.trimmed(record.version);
      return labelOnly(
        version ? `Pointed the design link at v${version}` : "Moved the design link"
      );
    }
  ]
];

// ../../packages/agents/src/tool-activity/factory/factory-tool-activity-describers.ts
var factoryToolActivityDescribers = [
  [HOST_TOOL_NAMES.SUBMIT_SPECS, () => labelOnly("Submitted the specs")],
  [HOST_TOOL_NAMES.SET_BRAND_KIT, () => labelOnly("Set the brand kit")],
  [HOST_TOOL_NAMES.PLAN_TICKETS, () => labelOnly("Planned the tickets")],
  [HOST_TOOL_NAMES.PLAN_FOLLOW_UPS, () => labelOnly("Planned follow-ups")],
  [
    HOST_TOOL_NAMES.MARK_PROVISIONING_STEP,
    (record) => {
      const name = toolInput.trimmed(record.name);
      const status = toolInput.trimmed(record.status);
      return labelOnly(
        name && status ? `Marked provisioning step ${name} ${status}` : "Reported a provisioning step"
      );
    }
  ],
  [HOST_TOOL_NAMES.DECIDE_MERGE, () => labelOnly("Settled the ticket's PR")],
  [HOST_TOOL_NAMES.DECIDE_QA, () => labelOnly("Settled the QA verdict")],
  [
    HOST_TOOL_NAMES.READ_FACTORY_SPECS,
    () => labelOnly("Read the signed specs")
  ],
  [
    HOST_TOOL_NAMES.REPORT_RELEASE_QA_FLOWS,
    () => labelOnly("Recorded the release QA flows")
  ],
  [
    HOST_TOOL_NAMES.REPORT_RELEASE_QA_RESULTS,
    () => labelOnly("Reported release QA results")
  ],
  [
    HOST_TOOL_NAMES.REPORT_QA_FLOW_RESULT,
    () => labelOnly("Reported a QA flow result")
  ],
  [
    HOST_TOOL_NAMES.DECIDE_RELEASE_QA,
    () => labelOnly("Settled the release QA round")
  ]
];

// ../../packages/agents/src/tool-activity/git/git-verbs-constants.ts
var GIT_VERBS = {
  LIST: { done: "Listed", running: "Listing", failed: "Could not list" },
  CLONE: { done: "Cloned", running: "Cloning", failed: "Could not clone" },
  SWITCH: {
    done: "Switched to",
    running: "Switching to",
    failed: "Could not switch to"
  },
  FETCH: { done: "Fetched", running: "Fetching", failed: "Could not fetch" },
  CREATE: { done: "Created", running: "Creating", failed: "Could not create" },
  SET_UP: {
    done: "Set up",
    running: "Setting up",
    failed: "Could not set up"
  },
  COMMIT: {
    done: "Committed",
    running: "Committing",
    failed: "Could not commit"
  },
  PUSH: { done: "Pushed", running: "Pushing", failed: "Could not push" },
  OPEN: { done: "Opened", running: "Opening", failed: "Could not open" },
  SUBMIT: {
    done: "Submitted",
    running: "Submitting",
    failed: "Could not submit"
  },
  RESTACK: {
    done: "Restacked",
    running: "Restacking",
    failed: "Could not restack"
  },
  CONTINUE_RESTACK: {
    done: "Continued restacking",
    running: "Continuing to restack",
    failed: "Could not continue restacking"
  },
  REVIEW: {
    done: "Reviewed",
    running: "Reviewing",
    failed: "Could not review"
  },
  COMMENT: {
    done: "Commented",
    running: "Commenting",
    failed: "Could not comment"
  },
  REPLY: { done: "Replied", running: "Replying", failed: "Could not reply" },
  RESOLVE: {
    done: "Resolved",
    running: "Resolving",
    failed: "Could not resolve"
  },
  ASK_MERGE: {
    done: "Asked to merge",
    running: "Asking to merge",
    failed: "Could not ask to merge"
  },
  ASK_DEPLOY: {
    done: "Asked to deploy",
    running: "Asking to deploy",
    failed: "Could not ask to deploy"
  }
};

// ../../packages/agents/src/tool-activity/git/requested-repos.ts
function requestedRepos(input) {
  const seen = /* @__PURE__ */ new Set();
  const names = [];
  for (const raw of [input.repoFullName, ...input.repoFullNames ?? []]) {
    const name = raw?.trim();
    if (!name || seen.has(name.toLowerCase())) continue;
    seen.add(name.toLowerCase());
    names.push(name);
  }
  return names;
}

// ../../packages/agents/src/tool-activity/git/verb-label.ts
function verbLabel(state, verbs, target) {
  return stateLabel(state, {
    done: `${verbs.done} ${target}`,
    running: `${verbs.running} ${target}`,
    failed: `${verbs.failed} ${target}`
  });
}

// ../../packages/agents/src/tool-activity/git/describe-clone-repo.ts
function describeCloneRepo(record, state) {
  const names = requestedRepos({
    repoFullName: typeof record.repoFullName === "string" ? record.repoFullName : void 0,
    repoFullNames: Array.isArray(record.repoFullNames) ? record.repoFullNames.filter((name) => typeof name === "string") : void 0
  });
  const target = names.length > 1 ? `${names.length} repositories` : names[0] ?? "the repository";
  return verbLabel(state, GIT_VERBS.CLONE, target);
}

// ../../packages/agents/src/tool-activity/git/git-tool-names-constants.ts
var GIT_TOOL_NAMES = {
  LIST_REPOS: "list_repos",
  CLONE_REPO: "clone_repo",
  REBIND_REPO: "rebind_repo",
  FETCH_PR_REF: "fetch_pr_ref",
  CLONE_TEMPLATE: "clone_template",
  CREATE_REPO: "create_repo",
  CREATE_BRANCH: "create_branch",
  GIT_COMMIT: "git_commit",
  GIT_PUSH: "git_push",
  OPEN_PULL_REQUEST: "open_pull_request",
  GRAPHITE_SUBMIT: "graphite_submit",
  GITHUB_RESTACK: "github_restack",
  GRAPHITE_RESTACK: "graphite_restack",
  SUBMIT_PR_REVIEW: "submit_pr_review",
  ADD_PR_COMMENT: "add_pr_comment",
  RESOLVE_REVIEW_THREAD: "resolve_review_thread",
  REQUEST_MERGE: "request_merge",
  REQUEST_DEPLOY: "request_deploy"
};

// ../../packages/agents/src/tool-activity/git/git-target.ts
var gitTarget = {
  pullRequest: (value) => typeof value === "number" && Number.isInteger(value) && value > 0 ? `PR #${value}` : null,
  onPullRequest: (value) => `on ${gitTarget.pullRequest(value) ?? "a pull request"}`,
  named: (value) => {
    const text = toolInput.trimmed(value);
    return text ? toolInput.truncate(text, 60) : null;
  },
  quoted: (value) => {
    const text = toolInput.trimmed(value)?.split("\n")[0]?.trim();
    return text ? `"${toolInput.truncate(text, 60)}"` : null;
  }
};

// ../../packages/agents/src/tool-activity/git/git-tool-activity-describers.ts
var gitToolActivityDescribers = [
  [
    GIT_TOOL_NAMES.LIST_REPOS,
    (_, state) => verbLabel(state, GIT_VERBS.LIST, "the repositories")
  ],
  [GIT_TOOL_NAMES.CLONE_REPO, describeCloneRepo],
  [
    GIT_TOOL_NAMES.REBIND_REPO,
    (record, state) => verbLabel(
      state,
      GIT_VERBS.SWITCH,
      gitTarget.named(record.repoFullName) ?? "another repository"
    )
  ],
  [
    GIT_TOOL_NAMES.FETCH_PR_REF,
    (record, state) => verbLabel(
      state,
      GIT_VERBS.FETCH,
      gitTarget.pullRequest(record.pullNumber) ?? "a pull request"
    )
  ],
  [
    GIT_TOOL_NAMES.CLONE_TEMPLATE,
    (_, state) => verbLabel(state, GIT_VERBS.CLONE, "the project template")
  ],
  [
    GIT_TOOL_NAMES.CREATE_REPO,
    (record, state) => {
      const name = gitTarget.named(record.name);
      return verbLabel(
        state,
        GIT_VERBS.CREATE,
        name ? `repository ${name}` : "the repository"
      );
    }
  ],
  [
    GIT_TOOL_NAMES.CREATE_BRANCH,
    // It may resume an existing branch or prefix the name, so neither is claimed.
    (_, state) => verbLabel(state, GIT_VERBS.SET_UP, "the working branch")
  ],
  [
    GIT_TOOL_NAMES.GIT_COMMIT,
    (record, state) => verbLabel(
      state,
      GIT_VERBS.COMMIT,
      gitTarget.quoted(record.message) ?? "changes"
    )
  ],
  [
    GIT_TOOL_NAMES.GIT_PUSH,
    (record, state) => verbLabel(
      state,
      GIT_VERBS.PUSH,
      gitTarget.named(record.branch) ?? "a branch"
    )
  ],
  [
    GIT_TOOL_NAMES.OPEN_PULL_REQUEST,
    (record, state) => {
      const title = gitTarget.quoted(record.title);
      return verbLabel(
        state,
        GIT_VERBS.OPEN,
        title ? `PR ${title}` : "a pull request"
      );
    }
  ],
  [
    GIT_TOOL_NAMES.GRAPHITE_SUBMIT,
    (_, state) => verbLabel(state, GIT_VERBS.SUBMIT, "the Graphite stack")
  ],
  [
    GIT_TOOL_NAMES.GITHUB_RESTACK,
    (record, state) => verbLabel(
      state,
      record.continue === true ? GIT_VERBS.CONTINUE_RESTACK : GIT_VERBS.RESTACK,
      gitTarget.pullRequest(record.prNumber) ?? "the stack"
    )
  ],
  [
    GIT_TOOL_NAMES.GRAPHITE_RESTACK,
    (record, state) => verbLabel(
      state,
      record.continue === true ? GIT_VERBS.CONTINUE_RESTACK : GIT_VERBS.RESTACK,
      "the Graphite stack"
    )
  ],
  [
    GIT_TOOL_NAMES.SUBMIT_PR_REVIEW,
    (record, state) => verbLabel(
      state,
      GIT_VERBS.REVIEW,
      gitTarget.pullRequest(record.pullNumber) ?? "a pull request"
    )
  ],
  [
    GIT_TOOL_NAMES.ADD_PR_COMMENT,
    (record, state) => typeof record.inReplyTo === "number" ? verbLabel(
      state,
      GIT_VERBS.REPLY,
      `to a review comment ${gitTarget.onPullRequest(record.pullNumber)}`
    ) : verbLabel(
      state,
      GIT_VERBS.COMMENT,
      gitTarget.onPullRequest(record.pullNumber)
    )
  ],
  [
    GIT_TOOL_NAMES.RESOLVE_REVIEW_THREAD,
    (record, state) => verbLabel(
      state,
      GIT_VERBS.RESOLVE,
      `a review thread ${gitTarget.onPullRequest(record.pullNumber)}`
    )
  ],
  [
    GIT_TOOL_NAMES.REQUEST_MERGE,
    (record, state) => verbLabel(
      state,
      GIT_VERBS.ASK_MERGE,
      gitTarget.pullRequest(record.prNumber) ?? "a pull request"
    )
  ],
  [
    GIT_TOOL_NAMES.REQUEST_DEPLOY,
    (_, state) => verbLabel(state, GIT_VERBS.ASK_DEPLOY, "to production")
  ]
];

// ../../packages/agents/src/tool-activity/interactive-view/describe-interactive-view.ts
function describeInteractiveView(verb) {
  return (record, state) => {
    const title = toolInput.trimmed(record.title);
    const subject = title ? `"${title}"` : "an interactive view";
    if (state.hasFailed) return labelOnly(`${verb.failed} ${subject}`);
    return labelOnly(
      `${state.isRunning ? verb.running : verb.done} ${subject}`
    );
  };
}

// ../../packages/agents/src/tool-activity/interactive-view/interactive-view-tool-activity-describers.ts
var interactiveViewToolActivityDescribers = [
  [
    HOST_TOOL_NAMES.SHOW_INTERACTIVE_VIEW,
    describeInteractiveView({
      running: "Showing",
      done: "Showed",
      failed: "Failed to show"
    })
  ],
  [
    HOST_TOOL_NAMES.PREVIEW_INTERACTIVE_VIEW,
    describeInteractiveView({
      running: "Previewing",
      done: "Previewed",
      failed: "Failed to preview"
    })
  ]
];

// ../../packages/agents/src/tool-activity/native/describe-tool-search.ts
function describeToolSearch(record, state) {
  const query = toolInput.trimmed(record.query);
  if (!query?.startsWith("select:")) {
    const target2 = query ? `the tools for "${toolInput.truncate(query, 60)}"` : "the available tools";
    return stateLabel(state, {
      done: `Searched ${target2}`,
      running: `Searching ${target2}`,
      failed: `Could not search ${target2}`
    });
  }
  const names = query.slice("select:".length).split(",").map((name) => name.trim()).filter(Boolean);
  const target = names.length === 1 ? `the ${toolInput.truncate(names[0] ?? "", 60)} tool` : `${names.length} tools`;
  return stateLabel(state, {
    done: `Loaded ${target}`,
    running: `Loading ${target}`,
    failed: `Could not load ${target}`
  });
}

// ../../packages/agents/src/tool-activity/native/native-tool-activity-describers.ts
var nativeToolActivityDescribers = [
  ["toolsearch", describeToolSearch],
  [
    "readlints",
    (_, state) => stateLabel(state, {
      done: "Read the lint diagnostics",
      running: "Reading the lint diagnostics",
      failed: "Could not read the lint diagnostics"
    })
  ]
];

// ../../packages/agents/src/tool-activity/review/review-tool-activity-describers.ts
var reviewToolActivityDescribers = [
  [HOST_TOOL_NAMES.VISUAL_TESTS_RUN, () => labelOnly("Ran the visual tests")],
  [HOST_TOOL_NAMES.VISUAL_TESTS_STATUS, () => labelOnly("Read the visual run")],
  [
    HOST_TOOL_NAMES.REVIEW_SCOPE,
    (record) => {
      const base = toolInput.str(record.base);
      const head = toolInput.str(record.head);
      return labelOnly(
        base && head ? `Scoped the review of ${toolInput.truncate(`${base}...${head}`, 60)}` : "Scoped the review"
      );
    }
  ],
  [
    HOST_TOOL_NAMES.FILTER_REVIEW_FINDINGS,
    (record) => {
      const count = Array.isArray(record.findings) ? record.findings.length : 0;
      return labelOnly(
        count > 0 ? `Fact-checked ${count} review finding${count === 1 ? "" : "s"}` : "Fact-checked the review findings"
      );
    }
  ],
  [
    HOST_TOOL_NAMES.SUBMIT_REVIEW_SUMMARY,
    () => labelOnly("Drew the change summary")
  ]
];

// ../../packages/agents/src/tool-activity/slides/slides-tool-activity-describers.ts
var slidesToolActivityDescribers = [
  [HOST_TOOL_NAMES.DECK_LIST, () => labelOnly("Listed the decks")],
  [
    HOST_TOOL_NAMES.DECK_CREATE,
    (record) => {
      const title = toolInput.trimmed(record.title);
      return labelOnly(
        title ? `Created the deck \u201C${title}\u201D` : "Created a deck"
      );
    }
  ],
  [HOST_TOOL_NAMES.DECK_READ, () => labelOnly("Read the deck")],
  [
    HOST_TOOL_NAMES.DECK_WRITE_SLIDE,
    (record) => labelOnly(
      toolInput.trimmed(record.slideId) ? "Rewrote a slide" : "Added a slide"
    )
  ],
  [HOST_TOOL_NAMES.DECK_MOVE_SLIDE, () => labelOnly("Moved a slide")],
  [HOST_TOOL_NAMES.DECK_DELETE_SLIDE, () => labelOnly("Deleted a slide")],
  [HOST_TOOL_NAMES.DECK_RENDER, () => labelOnly("Rendered the deck")]
];

// ../../packages/agents/src/tool-activity/gpu/gpu-tool-activity-describers.ts
var gpuToolActivityDescribers = [
  [HOST_TOOL_NAMES.GPU_ATTACH, () => labelOnly("Attached a GPU")],
  [HOST_TOOL_NAMES.GPU_PUSH, () => labelOnly("Sent files to the GPU")],
  [HOST_TOOL_NAMES.GPU_EXEC, () => labelOnly("Ran a command on the GPU")],
  [HOST_TOOL_NAMES.GPU_PULL, () => labelOnly("Fetched files from the GPU")],
  [HOST_TOOL_NAMES.GPU_STATUS, () => labelOnly("Checked the GPU")],
  [HOST_TOOL_NAMES.GPU_DETACH, () => labelOnly("Released the GPU")],
  [HOST_TOOL_NAMES.NOTEBOOK_OPEN, () => labelOnly("Opened JupyterLab")],
  [HOST_TOOL_NAMES.NOTEBOOK_RUN, () => labelOnly("Ran a notebook")],
  [HOST_TOOL_NAMES.NOTEBOOK_STATUS, () => labelOnly("Checked a notebook run")],
  [
    HOST_TOOL_NAMES.GPU_KERNEL_CONNECT,
    () => labelOnly("Switched the notebook to the GPU")
  ],
  [
    HOST_TOOL_NAMES.GPU_KERNEL_DISCONNECT,
    () => labelOnly("Switched the notebook back to the CPU")
  ]
];

// ../../packages/agents/src/tool-activity/model3d/model3d-tool-activity-describers.ts
var model3dToolActivityDescribers = [
  [HOST_TOOL_NAMES.MODEL3D_LIST, () => labelOnly("Listed the 3D models")],
  [HOST_TOOL_NAMES.MODEL3D_CREATE, () => labelOnly("Created a 3D model")],
  [HOST_TOOL_NAMES.MODEL3D_READ, () => labelOnly("Read the 3D model")],
  [
    HOST_TOOL_NAMES.MODEL3D_SOURCE_RESTORE,
    () => labelOnly("Restored the Blender source")
  ],
  [
    HOST_TOOL_NAMES.MODEL3D_PUBLISH,
    () => labelOnly("Published a 3D model version")
  ]
];

// ../../packages/agents/src/tool-activity/shader/shader-tool-activity-describers.ts
var shaderToolActivityDescribers = [
  [HOST_TOOL_NAMES.SHADER_LIST, () => labelOnly("Listed the shaders")],
  [HOST_TOOL_NAMES.SHADER_CREATE, () => labelOnly("Created a shader")],
  [HOST_TOOL_NAMES.SHADER_READ, () => labelOnly("Read the shader")],
  [
    HOST_TOOL_NAMES.SHADER_PUBLISH,
    () => labelOnly("Published a shader version")
  ]
];

// ../../packages/agents/src/tool-activity/video/video-tool-activity-describers.ts
var videoToolActivityDescribers = [
  [HOST_TOOL_NAMES.VIDEO_LIST, () => labelOnly("Listed the videos")],
  [HOST_TOOL_NAMES.VIDEO_CREATE, () => labelOnly("Created a video")],
  [HOST_TOOL_NAMES.VIDEO_READ, () => labelOnly("Read the video")],
  [
    HOST_TOOL_NAMES.VIDEO_SOURCE_RESTORE,
    () => labelOnly("Restored the video source")
  ],
  [HOST_TOOL_NAMES.VIDEO_PUBLISH, () => labelOnly("Published a video version")],
  [HOST_TOOL_NAMES.GENERATE_VOICE, () => labelOnly("Generated narration")],
  [HOST_TOOL_NAMES.GENERATE_MUSIC, () => labelOnly("Generated music")],
  [
    HOST_TOOL_NAMES.GENERATE_SOUND_EFFECT,
    () => labelOnly("Generated a sound effect")
  ],
  [HOST_TOOL_NAMES.LIST_VOICES, () => labelOnly("Listed the voices")],
  [HOST_TOOL_NAMES.VIDEO_SET_VOICES, () => labelOnly("Set the voices")]
];

// ../../packages/agents/src/tool-activity/video/labs-tool-activity-describers.ts
var labsToolActivityDescribers = [
  ...slidesToolActivityDescribers,
  ...videoToolActivityDescribers,
  ...model3dToolActivityDescribers,
  ...shaderToolActivityDescribers,
  ...gpuToolActivityDescribers
];

// ../../packages/agents/src/tool-activity/domain/domain-tool-activity-describers.ts
var domainToolActivityDescribers = [
  ...integrationToolActivityDescribers,
  ...browserToolActivityDescribers,
  ...designToolActivityDescribers,
  ...labsToolActivityDescribers,
  ...automationToolActivityDescribers,
  ...reviewToolActivityDescribers,
  ...databaseToolActivityDescribers,
  ...factoryToolActivityDescribers,
  ...interactiveViewToolActivityDescribers,
  ...gitToolActivityDescribers,
  ...nativeToolActivityDescribers
];

// ../../packages/agents/src/teams/agent-task-update-constants.ts
var AGENT_TASK_UPDATE_ACTIONS = {
  BLOCK: "block",
  COMPLETE: "complete",
  FAIL: "fail",
  HEARTBEAT: "heartbeat",
  PROGRESS: "progress",
  RELEASE: "release"
};
var OPEN_AGENT_TASK_UPDATE_ACTIONS = [
  AGENT_TASK_UPDATE_ACTIONS.HEARTBEAT,
  AGENT_TASK_UPDATE_ACTIONS.PROGRESS,
  AGENT_TASK_UPDATE_ACTIONS.RELEASE
];

// ../../packages/agents/src/tool-activity/canonical/update-task-labels.ts
var updateTaskLabels = new Map(
  Object.entries({
    [AGENT_TASK_UPDATE_ACTIONS.BLOCK]: "Blocked a task",
    [AGENT_TASK_UPDATE_ACTIONS.COMPLETE]: "Completed a task",
    [AGENT_TASK_UPDATE_ACTIONS.FAIL]: "Marked a task failed",
    [AGENT_TASK_UPDATE_ACTIONS.HEARTBEAT]: "Kept a task alive",
    [AGENT_TASK_UPDATE_ACTIONS.PROGRESS]: "Reported progress on a task",
    [AGENT_TASK_UPDATE_ACTIONS.RELEASE]: "Released a blocked task"
  })
);

// ../../packages/agents/src/tool-activity/canonical/describe-native-patch.ts
function describeNativePatch(record, state) {
  const path2 = toolActivityBasename(toolInput.str(record.path));
  const count = Array.isArray(record.changes) ? record.changes.length : 0;
  const target = path2 ?? (count > 0 ? `${count} file${count === 1 ? "" : "s"}` : "files");
  const verb = state.hasFailed ? "Failed to edit" : state.isRunning ? "Editing" : "Edited";
  return labelOnly(`${verb} ${target}`);
}

// ../../packages/agents/src/tool-activity/canonical/canonical-tool-activity-describers.ts
var canonicalToolActivityDescribers = new Map([
  ["apply_patch", describeNativePatch],
  [HOST_TOOL_NAMES.ADVISE, () => labelOnly("Advised the watched session")],
  [
    "read",
    (record) => labelOnly(
      `Read ${toolActivityBasename(toolInput.str(record.file_path) ?? toolInput.str(record.path)) ?? "a file"}`
    )
  ],
  [
    "write",
    (record) => labelOnly(
      `Wrote ${toolActivityBasename(toolInput.str(record.file_path) ?? toolInput.str(record.path)) ?? "a file"}`
    )
  ],
  [
    "edit",
    (record) => labelOnly(
      `Edited ${toolActivityBasename(toolInput.str(record.file_path) ?? toolInput.str(record.path)) ?? "a file"}`
    )
  ],
  [
    "bash",
    (record) => {
      const command = toolInput.verbatim(record.command) ?? toolInput.verbatim(record.cmd);
      const intent = toolInput.trimmed(record.description);
      if (!command && !intent) {
        return labelOnly("Ran a command");
      }
      return { text: intent, command };
    }
  ],
  [
    WORKSPACE_TOOL_NAMES.TASK_OUTPUT,
    () => labelOnly("Read a background command's output")
  ],
  [
    WORKSPACE_TOOL_NAMES.TASK_STOP,
    () => labelOnly("Stopped a background command")
  ],
  [
    "grep",
    (record) => {
      const pattern = toolInput.str(record.pattern) ?? toolInput.str(record.query);
      return labelOnly(
        pattern ? `Searched "${toolInput.truncate(pattern, 60)}"` : "Searched"
      );
    }
  ],
  [
    "glob",
    (record) => {
      const pattern = toolInput.str(record.pattern) ?? toolInput.str(record.glob);
      return labelOnly(
        pattern ? `Matched ${toolInput.truncate(pattern, 60)}` : "Matched files"
      );
    }
  ],
  [
    "ls",
    (record) => {
      const path2 = toolInput.str(record.path) ?? toolInput.str(record.dir);
      return labelOnly(path2 ? `Listed ${path2}` : "Listed a directory");
    }
  ],
  [
    "webfetch",
    (record) => {
      const host = toolActivityHostname(toolInput.str(record.url));
      return labelOnly(host ? `Fetched ${host}` : "Fetched a page");
    }
  ],
  [
    "websearch",
    (record) => {
      const query = toolInput.str(record.query) ?? toolInput.str(record.q);
      return labelOnly(
        query ? `Searched the web for "${toolInput.truncate(query, 60)}"` : "Searched the web"
      );
    }
  ],
  ["task", () => labelOnly("Ran a subagent")],
  [
    HOST_TOOL_NAMES.SET_STEPS,
    (record) => {
      const steps = Array.isArray(record.steps) ? record.steps.length : null;
      return labelOnly(
        steps ? `Planned ${steps} step${steps === 1 ? "" : "s"}` : "Planned the work"
      );
    }
  ],
  [
    HOST_TOOL_NAMES.SET_TODOS,
    (record) => {
      const step = toolInput.trimmed(record.step);
      return labelOnly(step ? `Updated the plan: ${step}` : "Updated the plan");
    }
  ],
  [
    HOST_TOOL_NAMES.SAVE_DEV_ENV_MANIFEST,
    () => labelOnly("Saved the dev environment manifest")
  ],
  [
    HOST_TOOL_NAMES.REQUEST_DEV_ENV_CREDENTIALS,
    (record) => {
      const asked = Array.isArray(record.credentials) ? record.credentials.length : null;
      return labelOnly(
        asked ? `Requested ${asked} credential${asked === 1 ? "" : "s"}` : "Requested credentials"
      );
    }
  ],
  [HOST_TOOL_NAMES.REQUEST_ENV_VARS, describeRequestEnvVars],
  [
    HOST_TOOL_NAMES.GENERATE_DEV_ENV_SECRET,
    (record) => labelOnly(
      typeof record.name === "string" ? `Generated ${record.name}` : "Generated a dev environment secret"
    )
  ],
  [
    HOST_TOOL_NAMES.SET_DEV_ENV_SIZING,
    () => labelOnly("Recorded the dev environment sizing")
  ],
  [HOST_TOOL_NAMES.BAKE_DEV_ENV, () => labelOnly("Baked the dev environment")],
  [HOST_TOOL_NAMES.TAKE_SCREENSHOT, describeTakeScreenshot],
  [HOST_TOOL_NAMES.RECORD_VIDEO, describeRecordVideo],
  [HOST_TOOL_NAMES.ATTACH_IMAGE, describeAttachImage],
  [HOST_TOOL_NAMES.ATTACH_FILE, describeAttachFile],
  [
    HOST_TOOL_NAMES.GET_IMAGE_GENERATORS,
    () => labelOnly("Listed the image models")
  ],
  [
    HOST_TOOL_NAMES.ASK_USER,
    (record) => {
      const asked = Array.isArray(record.questions) ? record.questions.length : null;
      return labelOnly(
        asked ? `Asked ${asked} question${asked === 1 ? "" : "s"}` : "Asked a question"
      );
    }
  ],
  [
    HOST_TOOL_NAMES.PROPOSE_PLAN,
    (record) => {
      const title = toolInput.trimmed(record.title);
      return labelOnly(
        title ? `Proposed a plan: ${toolInput.truncate(title, 60)}` : "Proposed a plan"
      );
    }
  ],
  [
    HOST_TOOL_NAMES.REQUEST_GUARDRAIL_APPROVAL,
    () => labelOnly("Asked for an approval")
  ],
  [
    HOST_TOOL_NAMES.REPORT_CONFIDENCE,
    () => labelOnly("Reported its confidence")
  ],
  [
    HOST_TOOL_NAMES.REPORT_HARNESS_ISSUE,
    (record) => {
      const title = toolInput.trimmed(record.title);
      return labelOnly(
        title ? `Reported a harness issue: ${toolInput.truncate(title, 60)}` : "Reported a harness issue"
      );
    }
  ],
  [HOST_TOOL_NAMES.MEMORY_SEARCH, describeMemorySearch],
  [HOST_TOOL_NAMES.REPORT_DEV_SERVER, describeReportDevServer],
  [HOST_TOOL_NAMES.MEMORY_WRITE, () => labelOnly("Wrote to its memory")],
  [
    HOST_TOOL_NAMES.READ_SESSION,
    (record) => {
      const goal = toolInput.str(record.goal);
      return labelOnly(
        goal ? `Read another chat: ${toolInput.truncate(goal, 60)}` : "Read another chat"
      );
    }
  ],
  [
    HOST_TOOL_NAMES.PROJECT_CONTEXT_READ,
    () => labelOnly("Read the project's shared context")
  ],
  [
    HOST_TOOL_NAMES.PROJECT_CONTEXT_WRITE,
    (record) => {
      const title = toolInput.verbatim(record.title);
      return title ? labelOnly(
        `Wrote to the project context: ${toolInput.truncate(title, 60)}`
      ) : labelOnly("Wrote to the project context");
    }
  ],
  [HOST_TOOL_NAMES.SLACK_SEND_MESSAGE, () => labelOnly("Posted to Slack")],
  [HOST_TOOL_NAMES.REPORT_FINDING, describeReportFinding],
  [HOST_TOOL_NAMES.LIST_FINDINGS, () => labelOnly("Read the known findings")],
  [
    HOST_TOOL_NAMES.RUN_VALIDATION,
    (record) => {
      const command = toolInput.verbatim(record.command);
      return command ? { text: "Ran a validation command", command } : labelOnly("Ran a validation command");
    }
  ],
  [
    HOST_TOOL_NAMES.CREATE_TASK,
    (record) => {
      const title = toolInput.trimmed(record.title);
      return labelOnly(
        title ? `Created task "${toolInput.truncate(title, 60)}"` : "Created a team task"
      );
    }
  ],
  [HOST_TOOL_NAMES.LIST_TASKS, () => labelOnly("Listed the team tasks")],
  [HOST_TOOL_NAMES.WORKFLOW, describeWorkflow],
  [HOST_TOOL_NAMES.PICKUP_TASK, () => labelOnly("Picked up a team task")],
  [
    HOST_TOOL_NAMES.UPDATE_TASK,
    (record) => {
      const action = toolInput.str(record.action);
      return labelOnly(
        (action ? updateTaskLabels.get(action) : null) ?? "Updated a team task"
      );
    }
  ],
  [
    HOST_TOOL_NAMES.DELIVERABLE_PUBLISH,
    () => labelOnly("Published a deliverable")
  ],
  [
    HOST_TOOL_NAMES.RENAME_SESSION,
    (record) => {
      const title = toolInput.trimmed(record.title);
      return labelOnly(
        title ? `Renamed the chat "${toolInput.truncate(title, 60)}"` : "Renamed the chat"
      );
    }
  ],
  [
    HOST_TOOL_NAMES.DECLARE_USER_FLOW,
    (record) => labelOnly(
      record.userFlow === true ? "Declared a user flow" : "Declared no user flow"
    )
  ],
  [HOST_TOOL_NAMES.GENERATE_IMAGE, describeGenerateImage],
  [HOST_TOOL_NAMES.ANNOTATE_IMAGE, describeAnnotateImage],
  ...domainToolActivityDescribers
]);

// ../../packages/agents/src/tool-activity/browser/soft-fail-browser-tools-constants.ts
var SOFT_FAIL_BROWSER_TOOLS = /* @__PURE__ */ new Set([
  HOST_TOOL_NAMES.BROWSER_NAVIGATE,
  HOST_TOOL_NAMES.BROWSER_SNAPSHOT,
  HOST_TOOL_NAMES.BROWSER_ACT,
  HOST_TOOL_NAMES.BROWSER_AUTOPILOT,
  HOST_TOOL_NAMES.BROWSER_WAIT,
  HOST_TOOL_NAMES.BROWSER_INSPECT,
  HOST_TOOL_NAMES.BROWSER_SCREENSHOT,
  HOST_TOOL_NAMES.BROWSER_RECORD_START,
  HOST_TOOL_NAMES.BROWSER_RECORD_STOP,
  HOST_TOOL_NAMES.BROWSER_CLOSE,
  HOST_TOOL_NAMES.REPL_EVAL,
  HOST_TOOL_NAMES.REPL_RESET
]);

// ../../packages/agents/src/tool-activity/browser/soft-failed-browser.ts
function softFailedBrowser(toolName, output) {
  return SOFT_FAIL_BROWSER_TOOLS.has(toolName) && typeof output === "string" && /^(?:browser|repl)_[a-z_]+ failed:/.test(output.trimStart());
}

// ../../packages/agents/src/tool-activity/capture/soft-fail-capture-tools-constants.ts
var SOFT_FAIL_CAPTURE_TOOLS = /* @__PURE__ */ new Set([
  HOST_TOOL_NAMES.TAKE_SCREENSHOT,
  HOST_TOOL_NAMES.RECORD_VIDEO,
  HOST_TOOL_NAMES.ATTACH_IMAGE,
  HOST_TOOL_NAMES.ATTACH_FILE,
  HOST_TOOL_NAMES.GENERATE_IMAGE,
  HOST_TOOL_NAMES.ANNOTATE_IMAGE,
  HOST_TOOL_NAMES.PREVIEW_INTERACTIVE_VIEW
]);

// ../../packages/agents/src/tool-activity/capture/soft-failed-capture.ts
function softFailedCapture(toolName, output) {
  if (!SOFT_FAIL_CAPTURE_TOOLS.has(toolName) || typeof output !== "string") {
    return false;
  }
  return output.trimStart().startsWith(`${toolName} failed`);
}

// ../../packages/agents/src/tool-activity/git/git-success-prefix-constants.ts
var GIT_SUCCESS_PREFIX = {
  [GIT_TOOL_NAMES.GIT_COMMIT]: "Committed",
  [GIT_TOOL_NAMES.GIT_PUSH]: "Pushed to",
  [GIT_TOOL_NAMES.REBIND_REPO]: "Switched to"
};

// ../../packages/agents/src/tool-activity/git/soft-failed-git.ts
function softFailedGit(toolName, output) {
  const prefix = GIT_SUCCESS_PREFIX[toolName];
  if (prefix === void 0 || typeof output !== "string") {
    return false;
  }
  return !output.trimStart().startsWith(prefix);
}

// ../../packages/agents/src/tool-activity/tool-activity-state-for.ts
function toolActivityStateFor(toolName, options) {
  const isRunning = options?.status === "running";
  return {
    isRunning,
    output: options?.output,
    hasFailed: options?.status === "failed" || !isRunning && (softFailedCapture(toolName, options?.output) || softFailedBrowser(toolName, options?.output) || softFailedGit(toolName, options?.output))
  };
}

// ../../packages/agents/src/tool-activity/skill/skill-call-constants.ts
var SKILL_NAME_KEYS = [
  "skill",
  "skill_name",
  "skillName",
  "name",
  "command"
];

// ../../packages/agents/src/tool-activity/skill/skill-name-from-input.ts
function skillNameFromInput(input) {
  if (!toolInput.isRecord(input)) {
    return null;
  }
  for (const key of SKILL_NAME_KEYS) {
    const value = input[key];
    if (typeof value === "string" && value.trim().length > 0) {
      return value.trim().split(/\s+/u)[0] ?? null;
    }
  }
  return null;
}

// ../../packages/agents/src/tool-activity/skill/string-field.ts
function stringField(input, key) {
  return toolInput.isRecord(input) ? toolInput.trimmed(input[key]) : null;
}

// ../../packages/agents/src/tool-activity/skill/registry-calls.ts
var registryCalls = {
  [HOST_TOOL_NAMES.SKILL_SEARCH]: (input) => ({
    kind: "search",
    query: stringField(input, "query")
  }),
  [HOST_TOOL_NAMES.SKILL_LOAD]: (input) => ({
    kind: "load",
    name: skillNameFromInput(input),
    via: "tool"
  }),
  [HOST_TOOL_NAMES.SAVE_SKILL]: (input) => ({
    kind: "save",
    name: stringField(input, "name")
  }),
  [HOST_TOOL_NAMES.REQUEST_SKILL_ACTIVATION]: (input) => ({
    kind: "activate",
    name: stringField(input, "name")
  }),
  [HOST_TOOL_NAMES.PROPOSE_SKILL_EDIT]: (input) => ({
    kind: "edit",
    name: stringField(input, "name")
  }),
  [HOST_TOOL_NAMES.RECORD_REVIEW_LESSON]: (input) => ({
    kind: "lesson",
    scope: stringField(input, "scope")
  })
};

// src/lib/event/format-tool-part.ts
function formatToolPart(part) {
  const { hasFailed } = toolActivityStateFor(part.toolName, {
    status: part.status,
    output: part.output
  });
  const status = hasFailed ? "failed" : part.status;
  return `${out.paint("tool", "dim")}   ${out.cell(part.toolName)} ${out.paint(`(${status})`, "dim")}`;
}

// src/lib/event/format-event.ts
function formatEvent(event, baseUrl) {
  const part = event.part;
  switch (part.kind) {
    case "text":
      if (part.authorUserId) {
        return `${out.paint("you", "magenta")}  ${truncate(part.text)}`;
      }
      if (part.source) {
        return out.paint(`system ${truncate(part.text, 400)}`, "dim");
      }
      return `${out.paint("agent", "blue")}  ${truncate(part.text)}`;
    case "reasoning":
      return out.paint(`think  ${truncate(part.text, 400)}`, "dim");
    case "tool":
      return formatToolPart(part);
    case "file_change":
      return `${out.paint("file", "dim")}   ${part.changeType.padEnd(8)} ${out.cell(part.path)}`;
    case "question":
      return out.paint(
        `ask    question round ${part.questionBatchId}: answer with \`hepha questions answer ${part.questionBatchId}\``,
        "yellow"
      );
    // No CLI verb decides a plan: the accept/refuse pair lives in the app.
    case "plan":
      return out.paint(
        `plan   plan ${part.planProposalId} awaits accept/refuse in the app`,
        "yellow"
      );
    case "approval":
      return out.paint(
        `gate   guardrail ${part.guardrailRequestId} needs a human: \`hepha approvals show ${part.guardrailRequestId}\``,
        "yellow"
      );
    case "media": {
      const target = part.url ?? part.pathname;
      return `${out.paint("media", "dim")}  ${part.mediaType}${part.caption ? `: ${out.cell(part.caption)}` : ""}${target ? `
       ${out.cell(target)}` : ""}`;
    }
    case "advisor":
      return formatAdvisorPart(part);
    case "deliverable":
      return formatDeliverablePart(part, event.sessionId, baseUrl);
    case "error":
      return out.paint(`error  ${truncate(part.message)}`, "red");
    case "external_event":
      return `${out.paint("event", "dim")}  ${out.cell(part.title)}${part.conclusion ? `: ${out.cell(part.conclusion)}` : ""}`;
    case "system":
      return out.paint(`system ${truncate(part.text, 300)}`, "dim");
    // The session's opening row: with the fenced prompt hidden, this is the
    // only place a CLI reader sees what the run was asked to do.
    case "source_context":
      return `${out.paint("source", "dim")} ${out.cell(
        [part.reference, part.title].filter(Boolean).join(" ")
      )}${part.url ? `: ${out.cell(part.url)}` : ""}${part.body ? `
       ${truncate(part.body, 400)}` : ""}`;
    case "verification":
      return `${out.paint("check", "dim")}  ${out.cell(part.name)} ${out.paint(`(${part.status})`, "dim")}`;
    default:
      return null;
  }
}

// src/lib/session/consume-session-events.ts
function consumeSessionEvents(events, cursor, quiet, baseUrl) {
  for (const event of events) {
    cursor = Math.max(cursor, event.sequence);
    if (quiet) {
      continue;
    }
    const line = formatEvent(event, baseUrl);
    if (line) {
      out.line(line);
    }
  }
  return cursor;
}

// src/lib/sleep/sleep.ts
function sleep2(seconds) {
  return new Promise((resolve) => setTimeout(resolve, seconds * 1e3));
}

// src/lib/sleep/sleep-constants.ts
var MAX_TIMER_MS = 2147483647;

// src/lib/sleep/timer-delay-ms.ts
function timerDelayMs(seconds) {
  return Math.min(Math.max(0, seconds) * 1e3, MAX_TIMER_MS);
}

// src/lib/sleep/interruptible-sleep.ts
function interruptibleSleep(seconds) {
  return new Promise((resolve, reject) => {
    const onInterrupt = () => {
      clearTimeout(timer);
      reject(
        new CliError("Follow interrupted.", 130, {
          code: CLI_ERROR_CODES.INTERRUPTED
        })
      );
    };
    const timer = setTimeout(() => {
      process.removeListener("SIGINT", onInterrupt);
      resolve();
    }, timerDelayMs(seconds));
    process.once("SIGINT", onInterrupt);
  });
}

// src/lib/session/default-follow-session-dependencies.ts
var defaultFollowSessionDependencies = {
  request: api,
  pollSleep: sleep2,
  retrySleep: interruptibleSleep,
  random: Math.random,
  // Monotonic: an NTP step backwards must not hand the outage budget more time
  // to burn, nor mute the periodic reminder until the clock catches up.
  now: () => performance.now(),
  warn: (message) => out.warn(message)
};

// src/lib/session/retry/follow-outage-message.ts
function followOutageMessage(warned) {
  return warned ? "Hepha API still unavailable; retrying follow." : "Hepha API unavailable; retrying follow.";
}

// src/lib/session/retry/follow-retry-constants.ts
var FOLLOW_RETRY_CAP_SECONDS = 22.5;
var FOLLOW_OUTAGE_FEEDBACK_MS = 5 * 60 * 1e3;
var RETRYABLE_FOLLOW_STATUS = /* @__PURE__ */ new Set([
  408,
  425,
  429,
  500,
  502,
  503,
  504
]);

// src/lib/session/retry/follow-retry-delay.ts
function followRetryDelay(error, failures, random) {
  const exponential = Math.min(
    FOLLOW_RETRY_CAP_SECONDS,
    2 ** Math.min(failures - 1, 30)
  );
  const jittered = exponential / 2 + random() * (exponential / 2);
  const retryAfter = Math.max(0, error.details.retryAfterSeconds ?? 0);
  return Math.max(jittered, retryAfter + random() * (exponential / 2));
}

// src/lib/session/retry/remaining-outage-seconds.ts
function remainingOutageSeconds(outageStartedAt, now, maxOutageMs) {
  if (maxOutageMs === null) {
    return Number.POSITIVE_INFINITY;
  }
  return (maxOutageMs - (now - outageStartedAt)) / 1e3;
}

// src/lib/session/retry/retryable-follow-error.ts
function retryableFollowError(error) {
  return error instanceof CliError && (RETRYABLE_FOLLOW_STATUS.has(error.details.status ?? 0) || error.details.code === CLI_ERROR_CODES.NETWORK || error.details.code === CLI_ERROR_CODES.TIMEOUT);
}

// src/lib/session/retry/should-report-follow-outage.ts
function shouldReportFollowOutage(lastWarnedAt, now) {
  return lastWarnedAt === null || now - lastWarnedAt >= FOLLOW_OUTAGE_FEEDBACK_MS;
}

// src/lib/session/retry/wait-for-follow-retry.ts
async function waitForFollowRetry(params) {
  const { error, retry, dependencies } = params;
  if (!retryableFollowError(error)) {
    throw error;
  }
  retry.failures += 1;
  const now = dependencies.now();
  retry.outageStartedAt ??= now;
  const delay = nextPollDelay(
    followRetryDelay(error, retry.failures, dependencies.random),
    remainingOutageSeconds(retry.outageStartedAt, now, params.maxOutageMs)
  );
  if (delay === null) {
    throw error;
  }
  if (shouldReportFollowOutage(retry.lastWarnedAt, now)) {
    dependencies.warn(followOutageMessage(retry.warned));
    retry.warned = true;
    retry.lastWarnedAt = now;
  }
  await dependencies.retrySleep(delay);
  if (remainingOutageSeconds(
    retry.outageStartedAt,
    dependencies.now(),
    params.maxOutageMs
  ) <= 0) {
    throw error;
  }
}

// src/lib/session/retry/request-with-follow-retry.ts
async function requestWithFollowRetry(params) {
  for (; ; ) {
    try {
      const result = await params.operation();
      if (params.retry.warned) {
        params.dependencies.warn("Hepha API recovered; continuing follow.");
      }
      params.retry.failures = 0;
      params.retry.warned = false;
      params.retry.lastWarnedAt = null;
      params.retry.outageStartedAt = null;
      return result;
    } catch (error) {
      await waitForFollowRetry({
        error,
        retry: params.retry,
        maxOutageMs: params.maxOutageMs,
        dependencies: params.dependencies
      });
    }
  }
}

// src/lib/blocked/blocked-constants.ts
var BLOCKED = /* @__PURE__ */ new Set([
  SESSION_STATUS.WAITING_HUMAN,
  SESSION_STATUS.AWAITING_APPROVAL
]);

// src/lib/terminal/terminal-constants.ts
var TERMINAL = /* @__PURE__ */ new Set([
  SESSION_STATUS.COMPLETED,
  SESSION_STATUS.FAILED,
  SESSION_STATUS.KILLED,
  SESSION_STATUS.ABSTAINED
]);

// src/lib/session/is-follow-stop-status.ts
function isFollowStopStatus(status) {
  return status !== void 0 && (TERMINAL.has(status) || BLOCKED.has(status));
}

// ../../packages/realtime/src/events/session/shared/session-status-constants.ts
var SESSION_SANDBOX_CAPACITY_WAIT_REASON = "No Ares machine has room for this sandbox. Waiting for capacity";

// ../../packages/realtime/src/events/session/capacity/is-sandbox-capacity-park.ts
function isSandboxCapacityPark(input) {
  return input.status === "waiting_human" && input.statusReason === SESSION_SANDBOX_CAPACITY_WAIT_REASON;
}

// src/lib/session/is-follow-stop-session.ts
function isFollowStopSession(session) {
  return isFollowStopStatus(session.status) && !isSandboxCapacityPark(session);
}

// src/lib/session/retry/resolve-follow-outage-budget.ts
function resolveFollowOutageBudget() {
  const raw = process.env.HEPHA_FOLLOW_MAX_OUTAGE_SECONDS?.trim();
  if (!raw) {
    return null;
  }
  const budgetMs = Number(raw) * 1e3;
  if (!Number.isFinite(budgetMs) || budgetMs <= 0) {
    throw new CliError(
      `HEPHA_FOLLOW_MAX_OUTAGE_SECONDS expects a positive number of seconds, got "${raw}".`,
      2
    );
  }
  return budgetMs;
}

// src/lib/session/retry/follow-request-timeout-ms.ts
function followRequestTimeoutMs(retry, maxOutageMs, now) {
  if (retry.outageStartedAt === null) {
    return resolveTimeout();
  }
  const remainingMs = remainingOutageSeconds(retry.outageStartedAt, now, maxOutageMs) * 1e3;
  return Math.max(1, Math.min(resolveTimeout(), remainingMs));
}

// src/lib/session/follow-session.ts
async function followSession(sessionId, options = {}, dependencies = defaultFollowSessionDependencies) {
  let cursor = options.after ?? 0;
  let emptyPolls = 0;
  const retry = {
    failures: 0,
    warned: false,
    lastWarnedAt: null,
    outageStartedAt: null
  };
  const maxOutageMs = resolveFollowOutageBudget();
  const { baseUrl } = config.load();
  for (; ; ) {
    const page = await requestWithFollowRetry({
      retry,
      maxOutageMs,
      dependencies,
      operation: async () => expectShape(
        await dependencies.request(path`/sessions/${sessionId}/events`, {
          query: { after: cursor || void 0, limit: PAGE_SIZE },
          timeoutMs: followRequestTimeoutMs(
            retry,
            maxOutageMs,
            dependencies.now()
          )
        }),
        "GET /sessions/{id}/events",
        {
          items: is.array,
          hasMore: (value) => typeof value === "boolean",
          nextCursor: (value) => value === null || is.number(value),
          // Optional: a deployment older than this binary omits it, and a
          // missing field here would be a non-retryable schema mismatch.
          status: is.optionalString
        }
      )
    });
    cursor = consumeSessionEvents(page.items, cursor, options.quiet, baseUrl);
    if (page.nextCursor !== null) {
      cursor = Math.max(cursor, page.nextCursor);
    }
    if (page.hasMore) {
      continue;
    }
    emptyPolls = page.items.length === 0 ? emptyPolls + 1 : 0;
    if (isFollowStopStatus(page.status) || page.items.length > 0 || emptyPolls >= 3) {
      const session = await requestWithFollowRetry({
        retry,
        maxOutageMs,
        dependencies,
        operation: async () => expectShape(
          await dependencies.request(path`/sessions/${sessionId}`, {
            timeoutMs: followRequestTimeoutMs(
              retry,
              maxOutageMs,
              dependencies.now()
            )
          }),
          "GET /sessions/{id}",
          { status: is.string }
        )
      });
      emptyPolls = 0;
      if (isFollowStopSession(session)) {
        return session;
      }
    }
    await dependencies.pollSleep(POLL_SECONDS);
  }
}

// src/lib/session/build-session-detail-rows.ts
function buildSessionDetailRows(session) {
  const rows = [["id", out.cell(session.id)]];
  if (session.statusReason) {
    rows.push(["reason", out.cell(session.statusReason)]);
  }
  if (session.repoFullName) {
    const branch = session.repos.find(
      (repo) => repo.repoFullName === session.repoFullName
    )?.branch;
    rows.push([
      "repo",
      out.cell(session.repoFullName + (branch ? ` @ ${branch}` : ""))
    ]);
  }
  rows.push(["task", session.taskType]);
  if (session.model) {
    rows.push(["model", out.cell(session.model)]);
  }
  if (session.prUrl) {
    rows.push([
      "pr",
      out.cell(
        `${session.prUrl}${session.prState ? ` (${session.prState})` : ""}`
      )
    ]);
  }
  for (const deliverable of session.deliverables) {
    if (deliverable.url && deliverable.url !== session.prUrl) {
      rows.push([out.cell(deliverable.type), out.cell(deliverable.url)]);
    }
  }
  return rows;
}

// src/lib/session/print-playbook-steps.ts
function printPlaybookSteps(steps) {
  if (steps.length === 0) {
    return;
  }
  out.line();
  for (const step of steps) {
    const done = step.todos.filter(
      (todo) => todo.status === "completed"
    ).length;
    const marker = done === step.todos.length && step.todos.length > 0 ? "\u2713" : "\xB7";
    out.line(
      `  ${marker} ${out.cell(step.title)} ${out.paint(`${done}/${step.todos.length}`, "dim")}`
    );
  }
}

// src/lib/session/print-session.ts
function printSession(session) {
  out.line(
    `${out.paint(out.cell(session.title), "bold")}  ${out.status(session.status)}`
  );
  const rows = buildSessionDetailRows(session);
  out.table(
    rows.map(([label, value]) => [out.paint(label ?? "", "dim"), value ?? ""])
  );
  printPlaybookSteps(session.playbook?.steps ?? []);
}

// src/lib/session/session-outcome.ts
function sessionOutcome(session) {
  switch (session.status) {
    case SESSION_STATUS.COMPLETED:
      return null;
    case SESSION_STATUS.WAITING_HUMAN:
      return new CliError(
        `Waiting on an answer: \`hepha questions list --session ${session.id}\``,
        3,
        {},
        session
      );
    case SESSION_STATUS.AWAITING_APPROVAL:
      return new CliError(
        "Waiting on a guardrail approval: `hepha approvals list`",
        3,
        {},
        session
      );
    default:
      return new CliError(
        `Session ended ${session.status}${session.statusReason ? `: ${session.statusReason}` : "."}`,
        1,
        {},
        session
      );
  }
}

// src/lib/session/write-followed-session-outcome.ts
function writeFollowedSessionOutcome(session, asJson) {
  const outcome = sessionOutcome(session);
  if (asJson) {
    if (!outcome) out.json(session);
  } else {
    out.line();
    printSession(session);
  }
  if (outcome) throw outcome;
}

// src/commands/harnesses/harnesses-constants.ts
var HARNESSES = [
  "claude-code",
  "codex",
  "cursor"
];

// src/commands/task/task-types-constants.ts
var TASK_TYPES = [
  "implement",
  "new-project",
  "orchestrator",
  "design",
  "automate"
];

// src/commands/command/create-run-input.ts
function createRunInput(parsed, prompt) {
  const taskType = args.choice(parsed, "task-type", TASK_TYPES);
  const harness = args.choice(parsed, "harness", HARNESSES);
  return {
    prompt,
    ...parsed.flags.title ? { title: parsed.flags.title } : {},
    ...taskType ? { taskType } : {},
    ...parsed.flags.model ? { modelSelection: parsed.flags.model } : {},
    ...harness ? { harnessPin: harness } : {},
    ...parsed.flags.repo ? { repoFullName: parsed.flags.repo } : {},
    ...parsed.flags.branch ? { branch: parsed.flags.branch } : {},
    ...parsed.repeated.skill ? { pinnedSkillIds: parsed.repeated.skill } : {}
  };
}

// src/commands/command/run-command.ts
async function runCommand(parsed) {
  args.expect(parsed, {
    flags: [
      "title",
      "task-type",
      "model",
      "harness",
      "repo",
      "branch",
      "skill"
    ],
    booleans: ["follow"],
    positional: 1
  });
  const prompt = args.required(parsed, 0, "prompt");
  const asJson = parsed.booleans.has("json");
  const body = createRunInput(parsed, prompt);
  const session = expectShape(
    await api("/sessions", { method: "POST", body }),
    "POST /sessions",
    { id: is.string, title: is.string, status: is.string }
  );
  if (!parsed.booleans.has("follow")) {
    if (asJson) {
      out.json(session);
      return;
    }
    printSession(session);
    out.line();
    out.line(out.paint(`  hepha sessions logs ${session.id} --follow`, "dim"));
    return;
  }
  const quiet = asJson || parsed.booleans.has("quiet");
  if (!quiet) {
    out.line(out.paint(`session ${session.id}`, "dim"));
    out.line();
  }
  const final = await followSession(session.id, { quiet });
  writeFollowedSessionOutcome(final, asJson);
}

// src/commands/sessions/sessions-constants.ts
var ACTIONS4 = [
  "list",
  "get",
  "logs",
  "send",
  "pause",
  "resume",
  "kill",
  "trash",
  "checks",
  "pr",
  "verify"
];
var CONTROL_PATHS = {
  pause: "pause",
  resume: "resume",
  kill: "kill"
};
var STATUS_GROUPS = ["active", "waiting", "done"];
var SESSION_FIELDS = {
  id: is.string,
  title: is.string,
  status: is.string,
  taskType: is.string,
  repos: is.array,
  deliverables: is.array
};

// src/commands/sessions/list-sessions.ts
async function listSessions(parsed) {
  args.expect(parsed, {
    flags: ["status", "repo", "search", "page", "limit"],
    booleans: ["all"],
    positional: 1
  });
  const result = expectShape(
    await api("/sessions", {
      query: {
        statusGroup: args.choice(parsed, "status", STATUS_GROUPS),
        repoFullName: parsed.flags.repo,
        search: parsed.flags.search,
        page: args.integer(parsed, "page", { min: 1 }),
        limit: args.integer(parsed, "limit", { min: 1, max: 100 }),
        scope: parsed.booleans.has("all") ? "all" : void 0
      }
    }),
    "GET /sessions",
    { data: is.array, "pagination.page": is.number }
  );
  if (parsed.booleans.has("json")) {
    out.json(result);
    return;
  }
  if (result.data.length === 0) {
    out.line("No sessions.");
    return;
  }
  if (result.pagination.hasNextPage) {
    out.line(
      out.paint(
        `page ${result.pagination.page}/${result.pagination.totalPages}: --page ${result.pagination.page + 1} for more`,
        "dim"
      )
    );
    out.line();
  }
  out.table(
    result.data.map((session) => [
      out.cell(session.id),
      out.status(session.status),
      out.cell(session.repoFullName ?? "-"),
      out.cell(session.title)
    ])
  );
}

// src/commands/session/send-session-message.ts
async function sendSessionMessage(parsed, asJson) {
  args.expect(parsed, { positional: 3 });
  const sessionId = args.required(parsed, 1, "sessionId");
  const message = args.required(parsed, 2, "message");
  const result = expectShape(
    await api(path`/sessions/${sessionId}/messages`, {
      method: "POST",
      body: { message }
    }),
    "POST /sessions/{id}/messages",
    { delivery: is.string }
  );
  if (asJson) {
    out.json(result);
    return;
  }
  out.line(
    result.delivery === "interrupt" ? "Delivered to the running agent." : "Queued: the agent picks it up at the end of its turn."
  );
}

// src/commands/show/follow-session-logs.ts
async function followSessionLogs(parsed, sessionId, after, asJson) {
  const session = await followSession(sessionId, {
    quiet: asJson || parsed.booleans.has("quiet"),
    ...after !== void 0 ? { after } : {}
  });
  writeFollowedSessionOutcome(session, asJson);
}

// src/commands/show/show-logs.ts
async function showLogs(parsed) {
  args.expect(parsed, {
    flags: ["after", "limit"],
    booleans: ["follow"],
    positional: 2
  });
  const sessionId = args.required(parsed, 1, "sessionId");
  const asJson = parsed.booleans.has("json");
  const after = args.integer(parsed, "after", { min: 0 });
  if (parsed.booleans.has("follow")) {
    await followSessionLogs(parsed, sessionId, after, asJson);
    return;
  }
  const page = expectShape(
    await api(path`/sessions/${sessionId}/events`, {
      query: {
        after,
        limit: args.integer(parsed, "limit", { min: 1, max: 200 })
      }
    }),
    "GET /sessions/{id}/events",
    { items: is.array }
  );
  if (asJson) {
    out.json(page);
    return;
  }
  const { baseUrl } = config.load();
  for (const event of page.items) {
    const line = formatEvent(event, baseUrl);
    if (line) {
      out.line(line);
    }
  }
  if (page.hasMore && page.nextCursor !== null) {
    out.line();
    out.line(
      out.paint(
        `more events: hepha sessions logs ${sessionId} --after ${page.nextCursor}`,
        "dim"
      )
    );
  }
}

// src/commands/sessions/session-actions.ts
var sessionActions = {
  list: (parsed) => listSessions(parsed),
  logs: (parsed) => showLogs(parsed),
  send: sendSessionMessage
};

// src/commands/sessions/trash-session.ts
async function trashSession(sessionId, asJson) {
  const result = expectShape(
    await api(path`/sessions/${sessionId}`, { method: "DELETE" }),
    "DELETE /sessions/{id}",
    {
      sessionId: (value) => value === sessionId,
      deletedAt: (value) => value === null || typeof value === "string" && Number.isFinite(Date.parse(value))
    }
  );
  if (asJson) {
    out.json(result);
    return;
  }
  out.line(`Trashed ${out.safe(result.sessionId)}`);
}

// src/commands/detail/detail.ts
function detail(payload, endpoint) {
  return expectShape(payload, endpoint, SESSION_FIELDS);
}

// src/commands/command/run-session-control-action.ts
async function runSessionControlAction(parsed, action, sessionId, asJson) {
  const controlPath = CONTROL_PATHS[action];
  if (controlPath === void 0) {
    throw new CliError(`Unknown session action: ${action}`, 2);
  }
  const session = detail(
    await api(path`/sessions/${sessionId}/${controlPath}`, {
      method: "POST",
      body: action === "kill" && parsed.flags.reason ? { reason: parsed.flags.reason } : {}
    }),
    `POST /sessions/{id}/${controlPath}`
  );
  if (asJson) {
    out.json(session);
    return;
  }
  printSession(session);
}

// src/commands/command/run-session-operation.ts
async function runSessionOperation(parsed, action, asJson) {
  args.expect(parsed, {
    positional: 2,
    ...action === "kill" ? { flags: ["reason"] } : {}
  });
  const sessionId = args.required(parsed, 1, "sessionId");
  if (action === "trash") {
    await trashSession(sessionId, asJson);
    return;
  }
  if (action === "get") {
    const session = detail(
      await api(path`/sessions/${sessionId}`),
      "GET /sessions/{id}"
    );
    if (asJson) {
      out.json(session);
      return;
    }
    printSession(session);
    return;
  }
  if (Object.hasOwn(CONTROL_PATHS, action)) {
    await runSessionControlAction(parsed, action, sessionId, asJson);
    return;
  }
  if (action === "checks") {
    out.json(
      await api(
        path`/sessions/${sessionId}/checks`,
        { method: "POST", body: {} }
      )
    );
    return;
  }
  if (action === "pr") {
    out.json(
      await api(
        path`/sessions/${sessionId}/pr-checks`
      )
    );
    return;
  }
  out.json(
    await api(
      path`/sessions/${sessionId}/verification`
    )
  );
}

// src/commands/command/sessions-command.ts
async function sessionsCommand(parsed) {
  const action = args.action(parsed, "sessions", ACTIONS4, "list");
  const asJson = parsed.booleans.has("json");
  const dedicated = sessionActions[action];
  if (dedicated) {
    return await dedicated(parsed, asJson);
  }
  await runSessionOperation(parsed, action, asJson);
}

// src/commands/command/whoami-command.ts
async function whoamiCommand(parsed) {
  args.expect(parsed, {});
  const identity = expectShape(
    await api("/me"),
    "GET /me",
    {
      "user.name": is.string,
      "user.email": is.string,
      "auth.method": is.string,
      "auth.scopes": is.array
    }
  );
  if (parsed.booleans.has("json")) {
    out.json(identity);
    return;
  }
  out.table([
    [
      out.paint("user", "dim"),
      out.cell(`${identity.user.name} <${identity.user.email}>`)
    ],
    [out.paint("role", "dim"), out.cell(identity.user.role ?? "member")],
    [
      out.paint("auth", "dim"),
      out.cell(
        identity.auth.method === "api_key" ? `api key${identity.auth.keyName ? ` "${identity.auth.keyName}"` : ""}` : "session"
      )
    ],
    [out.paint("scopes", "dim"), identity.auth.scopes.join(" ")],
    [out.paint("url", "dim"), config.load().baseUrl]
  ]);
}

// src/catalog-topics-constants.ts
var CATALOG_TOPICS = /* @__PURE__ */ new Set([
  "repos",
  "branches",
  "models",
  "skills"
]);

// src/dispatch.ts
async function dispatch(command, argv) {
  const parsed = args.parse(argv);
  if (parsed.booleans.has("help")) {
    helpCommand();
    return;
  }
  if (CATALOG_TOPICS.has(command)) {
    return await catalogCommand(command, parsed);
  }
  switch (command) {
    case "login":
      return await loginCommand(parsed);
    case "logout":
      return logoutCommand(parsed);
    case "whoami":
      return await whoamiCommand(parsed);
    case "keys":
      return await keysCommand(parsed);
    case "run":
      return await runCommand(parsed);
    case "sessions":
      return await sessionsCommand(parsed);
    case "questions":
      return await questionsCommand(parsed);
    case "approvals":
      return await approvalsCommand(parsed);
    default:
      helpCommand();
      throw new CliError(`Unknown command "${command}".`, 2);
  }
}

// src/main.ts
async function main() {
  const argv = process.argv.slice(2);
  out.setJson(wantsJson(argv));
  const [command, ...rest] = argv;
  if (!command || command === "help" || command === "--help") {
    helpCommand();
    return;
  }
  if (command === "--version" || command === "-v" || command === "version") {
    args.expect(args.parse(rest), {});
    if (out.isJson()) {
      out.json({ version: VERSION });
    } else {
      out.line(VERSION);
    }
    return;
  }
  await dispatch(command, rest);
}

// src/cli-entry.ts
async function cliEntry() {
  try {
    await main();
  } catch (error) {
    const failure = error instanceof CliError ? error : new CliError(error instanceof Error ? error.message : String(error));
    out.failure(failure);
    process.exit(failure.exitCode);
  }
}
void cliEntry();
export {
  cliEntry
};
//# sourceMappingURL=index.js.map