@impulselab/hepha
Version:
Command-line interface for the Hepha platform.
4,706 lines • 148 kB
JavaScript
#!/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