@pantheon.ai/agents 0.0.17 → 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (3) hide show
  1. package/README.md +8 -4
  2. package/dist/index.js +1134 -647
  3. package/package.json +3 -1
package/dist/index.js CHANGED
@@ -17034,8 +17034,9 @@ async function getPantheonBranchInfo({ projectId, branchId }) {
17034
17034
  }, null);
17035
17035
  }
17036
17036
  async function executeOnPantheon({ projectId, branchId, prompt, agent }) {
17037
+ const promptSequence = Array.isArray(prompt) ? prompt : [prompt];
17037
17038
  return (await executor.execute(createProjectExploration, { projectId }, {
17038
- shared_prompt_sequence: [prompt],
17039
+ shared_prompt_sequence: promptSequence,
17039
17040
  num_branches: 1,
17040
17041
  agent,
17041
17042
  parent_branch_id: branchId
@@ -17059,8 +17060,47 @@ function getTaskWorkingBranchId(task) {
17059
17060
  return ("branch_id" in task ? task.branch_id : void 0) ?? task.base_branch_id;
17060
17061
  }
17061
17062
 
17063
+ //#endregion
17064
+ //#region src/core/agent-config-utils.ts
17065
+ function normalizeRepoUrl(value) {
17066
+ return value.trim().replace(/\/+$/, "").replace(/\.git$/, "");
17067
+ }
17068
+ function toRawGitHubContentUrl(url) {
17069
+ if (!url.startsWith("https://github.com/")) return url;
17070
+ return url.replace("https://github.com/", "https://raw.githubusercontent.com/").replace("/blob/", "/");
17071
+ }
17072
+ function normalizeSkills(value) {
17073
+ if (Array.isArray(value)) return value.filter((item) => typeof item === "string");
17074
+ if (typeof value === "string") {
17075
+ try {
17076
+ const parsed = JSON.parse(value);
17077
+ if (Array.isArray(parsed)) return parsed.filter((item) => typeof item === "string");
17078
+ } catch {}
17079
+ return value.split(",").map((item) => item.trim()).filter((item) => item !== "");
17080
+ }
17081
+ return [];
17082
+ }
17083
+ function normalizeSetupScriptSlug(setupScript, fallback) {
17084
+ if (typeof setupScript === "string" && setupScript.trim() !== "") return setupScript.trim();
17085
+ return fallback.trim();
17086
+ }
17087
+ function buildSetupScriptStep(options) {
17088
+ const prototypeRepoUrl = normalizeRepoUrl(options.prototypeRepoUrl);
17089
+ const setupScriptUrl = toRawGitHubContentUrl(`${prototypeRepoUrl}/blob/main/scripts/${`setup-${options.setupScriptSlug}.sh`}`);
17090
+ const skillsArg = JSON.stringify(options.skills.join(","));
17091
+ return `execute shell ${setupScriptUrl} ${options.role.trim()} ${skillsArg} ${prototypeRepoUrl}`;
17092
+ }
17093
+
17062
17094
  //#endregion
17063
17095
  //#region src/core/task-list.ts
17096
+ function buildTaskPromptSequence(config, taskPrompt) {
17097
+ return [buildSetupScriptStep({
17098
+ prototypeRepoUrl: config.prototype_url,
17099
+ setupScriptSlug: normalizeSetupScriptSlug(config.setup_script, config.execute_agent),
17100
+ role: config.role,
17101
+ skills: normalizeSkills(config.skills)
17102
+ }), taskPrompt];
17103
+ }
17064
17104
  async function startTaskListLoop(agentName, { loopInterval = 5 }, logger) {
17065
17105
  logger.info("Starting task list loop...");
17066
17106
  let i = 0;
@@ -17134,11 +17174,12 @@ async function startPendingTask(provider, task, logger) {
17134
17174
  logger.warn("Agent configuration not found for project %s. Skip starting tasks.", taskToStart.project_id);
17135
17175
  return false;
17136
17176
  }
17177
+ const promptSequence = buildTaskPromptSequence(config, taskToStart.task);
17137
17178
  const taskBranch = await provider.startTask(taskToStart, async () => {
17138
17179
  return await executeOnPantheon({
17139
17180
  projectId: taskToStart.project_id,
17140
17181
  branchId: taskToStart.base_branch_id,
17141
- prompt: taskToStart.task,
17182
+ prompt: promptSequence,
17142
17183
  agent: config.execute_agent
17143
17184
  });
17144
17185
  });
@@ -17538,17 +17579,6 @@ var WatchStepAggregator = class {
17538
17579
 
17539
17580
  //#endregion
17540
17581
  //#region src/core/index.ts
17541
- function normalizeSkills(value) {
17542
- if (Array.isArray(value)) return value.filter((item) => typeof item === "string");
17543
- if (typeof value === "string") {
17544
- try {
17545
- const parsed = JSON.parse(value);
17546
- if (Array.isArray(parsed)) return parsed.filter((item) => typeof item === "string");
17547
- } catch {}
17548
- return value.split(",").map((item) => item.trim()).filter((item) => item !== "");
17549
- }
17550
- return [];
17551
- }
17552
17582
  function buildRebootstrapDiffPrompt(options) {
17553
17583
  return `You must follow these instructions":
17554
17584
  1. Clone the main branch from ${options.prototypeUrl} to a temporary directory (<pantheon-agents> in follow instructions references to this directory).
@@ -17635,75 +17665,21 @@ async function configAgent(name, options) {
17635
17665
  }
17636
17666
  options.rootBranchId = project.root_branch_id;
17637
17667
  }
17638
- if (options.bootstrap) {
17639
- const { branch_id: branchId, latest_snap_id: branchLatestSnapId } = await executeOnPantheon({
17640
- projectId: options.projectId,
17641
- branchId: options.rootBranchId,
17642
- agent: "codex",
17643
- prompt: `You must follow these instructions":
17644
- 1. Clone the main branch from ${options.prototypeUrl} to a temporary directory (<pantheon-agents> in follow instructions references to this directory).
17645
- 2. Copy the <pantheon-agents>/agents/${options.role}/AGENTS.md to \`<workspace>/AGENTS.md\`.
17646
- 3. Copy the <pantheon-agents>/agents/${options.role}/skills directory to \`<workspace>/.codex/skills\` if the source directory exists.
17647
- ${options.skills.length > 0 ? `4. Copy the pantheon-agents/skills/\{${options.skills.join(",")}} directory to \`<workspace>/.codex/skills/\`` : ""}
17648
-
17649
- Validate <workspace>: check if files and directorys exists:
17650
- - AGENTS.md
17651
- - .codex/skills
17652
-
17653
- **You MUST NOT generate AGENTS.md and skills by yourself if clone failed.**
17654
-
17655
- Finally, outputs the first 5 lines of <workspace>/AGENTS.md and the skills list in <workspace>/.codex/skills
17656
- `
17657
- });
17658
- let retried = 0;
17659
- const maxRetries = 3;
17660
- let i = 0;
17661
- console.log(`Bootstrap branch created: ${branchId} ${branchLatestSnapId}. Waiting for ready... [poll interval = 10s]`);
17662
- while (true) {
17663
- await new Promise((resolve) => {
17664
- setTimeout(resolve, 1e4);
17665
- });
17666
- const result = await getPantheonBranch({
17667
- branchId,
17668
- projectId: options.projectId,
17669
- getOutputIfFinished: true
17670
- }).then((result) => {
17671
- retried = 0;
17672
- return result;
17673
- }).catch((reason) => {
17674
- if (retried < maxRetries) {
17675
- retried++;
17676
- return { state: "others" };
17677
- }
17678
- throw new Error(`Failed to get bootstrap branch status. Retry ${retried} times. Last error: ${getErrorMessage$2(reason)}`);
17679
- });
17680
- if (result.state === "failed") {
17681
- console.error("Bootstrap failed: " + result.error);
17682
- process.exitCode = 1;
17683
- return;
17684
- }
17685
- if (result.state === "succeed") {
17686
- console.log("Bootstrap succeeded. Output is:");
17687
- console.log(result.output);
17688
- break;
17689
- }
17690
- console.log(`Bootstrap in progress... [${++i}]`);
17691
- }
17692
- await provider.setAgentConfig({
17693
- project_id: options.projectId,
17694
- base_branch_id: branchId,
17695
- execute_agent: options.executeAgent,
17696
- role: options.role,
17697
- skills: options.skills,
17698
- prototype_url: options.prototypeUrl
17699
- });
17700
- } else await provider.setAgentConfig({
17668
+ const rootBranchId = options.rootBranchId;
17669
+ if (!rootBranchId) {
17670
+ console.error("Unable to resolve root branch id.");
17671
+ process.exitCode = 1;
17672
+ return;
17673
+ }
17674
+ const setupScript = normalizeSetupScriptSlug(options.setupScript, options.executeAgent);
17675
+ await provider.setAgentConfig({
17701
17676
  project_id: options.projectId,
17702
- base_branch_id: options.rootBranchId,
17677
+ base_branch_id: rootBranchId,
17703
17678
  execute_agent: options.executeAgent,
17704
17679
  role: options.role,
17705
17680
  skills: options.skills,
17706
- prototype_url: options.prototypeUrl
17681
+ prototype_url: options.prototypeUrl,
17682
+ setup_script: setupScript
17707
17683
  });
17708
17684
  console.log(`Agent ${name} configured successfully.`);
17709
17685
  } finally {
@@ -17716,6 +17692,9 @@ async function reconfigAgentWithDeps(name, options, deps) {
17716
17692
  const resolvedRole = options.role?.trim() || previousConfig.role;
17717
17693
  if (options.role != null && !options.role.trim()) throw new Error("--role must be non-empty.");
17718
17694
  const resolvedSkills = options.skills ?? normalizeSkills(previousConfig.skills);
17695
+ const previousSetupScript = normalizeSetupScriptSlug(previousConfig.setup_script, previousConfig.execute_agent);
17696
+ const resolvedSetupScript = options.setupScript?.trim() || previousSetupScript;
17697
+ if (options.setupScript != null && !options.setupScript.trim()) throw new Error("--setup-script must be non-empty.");
17719
17698
  const { branch_id: nextBaseBranchId } = await deps.executeOnPantheonFn({
17720
17699
  projectId: options.projectId,
17721
17700
  branchId: previousConfig.base_branch_id,
@@ -17742,7 +17721,8 @@ async function reconfigAgentWithDeps(name, options, deps) {
17742
17721
  execute_agent: previousConfig.execute_agent,
17743
17722
  role: resolvedRole,
17744
17723
  skills: resolvedSkills,
17745
- prototype_url: previousConfig.prototype_url
17724
+ prototype_url: previousConfig.prototype_url,
17725
+ setup_script: resolvedSetupScript
17746
17726
  });
17747
17727
  return {
17748
17728
  previousConfig,
@@ -17963,15 +17943,32 @@ function ensureEnv(keys) {
17963
17943
  process.exitCode = 1;
17964
17944
  return false;
17965
17945
  }
17946
+ function normalizeOptional(value) {
17947
+ if (value == null) return void 0;
17948
+ const trimmed = value.trim();
17949
+ return trimmed === "" ? void 0 : trimmed;
17950
+ }
17951
+ function resolvePantheonProjectId(projectIdArg) {
17952
+ return normalizeOptional(projectIdArg) ?? normalizeOptional(process.env.DEFAULT_PANTHEON_PROJECT_ID);
17953
+ }
17954
+ function resolvePantheonRootBranchId(rootBranchIdArg) {
17955
+ return normalizeOptional(rootBranchIdArg) ?? normalizeOptional(process.env.DEFAULT_PANTHEON_ROOT_BRANCH_ID);
17956
+ }
17966
17957
 
17967
17958
  //#endregion
17968
17959
  //#region src/cli/commands/add-task.ts
17969
- function createAddTaskCommand(version) {
17970
- return createCommand("add-task").version(version).description("Add a task to an agent").argument("<name>", "The name of the agent.").argument("<project-id>", "The project id of the agent.").argument("<task-prompt>", "The prompt of the task.").option("--parent-task-id <task-id>", "Parent task id for dependent tasks.").action(async function() {
17971
- const [name, projectId, taskPrompt] = this.args;
17960
+ function createAddTaskCommand(version, deps = {}) {
17961
+ return createCommand("add-task").version(version).description("Add a task to an agent").argument("<name>", "The name of the agent.").argument("<task-prompt>", "The prompt of the task.").option("--project-id <project-id>", "The project id of the agent. Defaults to DEFAULT_PANTHEON_PROJECT_ID.").option("--parent-task-id <task-id>", "Parent task id for dependent tasks.").action(async function() {
17962
+ const [name, taskPrompt] = this.args;
17972
17963
  const options = this.opts();
17964
+ const projectId = resolvePantheonProjectId(options.projectId);
17965
+ if (!projectId) {
17966
+ console.error("project-id is required (--project-id or DEFAULT_PANTHEON_PROJECT_ID).");
17967
+ process$1.exitCode = 1;
17968
+ return;
17969
+ }
17973
17970
  if (!ensureEnv(["DATABASE_URL"])) return;
17974
- await addTask(name, {
17971
+ await (deps.addTask ?? addTask)(name, {
17975
17972
  projectId,
17976
17973
  prompt: taskPrompt,
17977
17974
  parentTaskId: options.parentTaskId
@@ -18024,19 +18021,103 @@ function createCancelTaskCommand(version) {
18024
18021
  });
18025
18022
  }
18026
18023
 
18024
+ //#endregion
18025
+ //#region src/cli/utils/parse.ts
18026
+ function parseCommaList(value) {
18027
+ return value.split(",").map((item) => item.trim()).filter((item) => item !== "");
18028
+ }
18029
+ function parseUniqueCommaList(value) {
18030
+ const items = parseCommaList(value);
18031
+ const seen = /* @__PURE__ */ new Set();
18032
+ const result = [];
18033
+ for (const item of items) {
18034
+ if (seen.has(item)) continue;
18035
+ seen.add(item);
18036
+ result.push(item);
18037
+ }
18038
+ return result;
18039
+ }
18040
+ function isLeapYear(year) {
18041
+ return year % 4 === 0 && year % 100 !== 0 || year % 400 === 0;
18042
+ }
18043
+ function daysInMonth(year, month) {
18044
+ switch (month) {
18045
+ case 2: return isLeapYear(year) ? 29 : 28;
18046
+ case 4:
18047
+ case 6:
18048
+ case 9:
18049
+ case 11: return 30;
18050
+ default: return 31;
18051
+ }
18052
+ }
18053
+ function parseIsoTimestamp(value) {
18054
+ const invalid = (detail) => ({
18055
+ ok: false,
18056
+ error: `Invalid timestamp: ${JSON.stringify(value)}. ` + (detail ? `${detail} ` : "") + "Expected ISO-8601 date-time with timezone, e.g. 2026-02-12T00:00:00Z."
18057
+ });
18058
+ const input = value.trim();
18059
+ const match = /^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2})(?:\.(\d{1,3}))?(Z|([+-])(\d{2}):(\d{2}))$/.exec(input);
18060
+ if (!match) return invalid();
18061
+ const year = Number(match[1]);
18062
+ const month = Number(match[2]);
18063
+ const day = Number(match[3]);
18064
+ const hour = Number(match[4]);
18065
+ const minute = Number(match[5]);
18066
+ const second = Number(match[6]);
18067
+ const fraction = match[7];
18068
+ if (month < 1 || month > 12) return invalid();
18069
+ const dim = daysInMonth(year, month);
18070
+ if (day < 1 || day > dim) return invalid("Day is out of range for month.");
18071
+ if (hour < 0 || hour > 23) return invalid("Hour must be between 00 and 23.");
18072
+ if (minute < 0 || minute > 59) return invalid("Minute must be between 00 and 59.");
18073
+ if (second < 0 || second > 59) return invalid("Second must be between 00 and 59.");
18074
+ const millis = fraction ? Number(fraction.padEnd(3, "0")) : 0;
18075
+ if (!Number.isInteger(millis) || millis < 0 || millis > 999) return invalid("Fractional seconds must have 1-3 digits.");
18076
+ const timezone = match[8];
18077
+ let offsetMinutes = 0;
18078
+ if (timezone !== "Z") {
18079
+ const sign = match[9];
18080
+ const offsetHours = Number(match[10]);
18081
+ const offsetMins = Number(match[11]);
18082
+ if (sign !== "+" && sign !== "-" || offsetHours < 0 || offsetHours > 23 || offsetMins < 0 || offsetMins > 59) return invalid("Timezone offset must be between -23:59 and +23:59.");
18083
+ offsetMinutes = (sign === "+" ? 1 : -1) * (offsetHours * 60 + offsetMins);
18084
+ }
18085
+ const utcMillis = Date.UTC(year, month - 1, day, hour, minute, second, millis) - offsetMinutes * 6e4;
18086
+ const date = new Date(utcMillis);
18087
+ if (Number.isNaN(date.getTime())) return invalid();
18088
+ return {
18089
+ ok: true,
18090
+ value: date
18091
+ };
18092
+ }
18093
+
18027
18094
  //#endregion
18028
18095
  //#region src/cli/commands/config.ts
18096
+ function parseSetupScriptSlug(value) {
18097
+ const slug = value.trim();
18098
+ if (!slug) throw new InvalidArgumentError("setup-script slug must be non-empty.");
18099
+ return slug;
18100
+ }
18029
18101
  function createConfigAgentCommand(version) {
18030
- return createCommand("config").version(version).description("Configure agent for pantheon project").argument("<name>", "The name of the agent.").argument("<role>", "The role of the agent.").argument("<project-id>", "The project id of the agent.").option("--skills <skills>", "The skills of the agent. Multiple values are separated by comma.", (val) => val.split(",").map((s) => s.trim()).filter((s) => s !== ""), []).option("--execute-agent <agent>", "The execute agent of the agent.", "codex").option("--root-branch-id <branchId>", "The root branch id of the agent. Default to project root branch id.").option("--prototype-url <url>", "Role and skill definitions repo.", "https://github.com/pingcap-inc/pantheon-agents").option("--no-bootstrap", "Prevent bootstrap base branch for agent. Use the root branch as base branch.").action(async function() {
18031
- const [name, role, projectId] = this.args;
18102
+ return createCommand("config").version(version).description("Configure agent for pantheon project").argument("<name>", "The name of the agent.").argument("<role>", "The role of the agent.").option("--project-id <project-id>", "The project id of the agent. Defaults to DEFAULT_PANTHEON_PROJECT_ID.").option("--skills <skills>", "The skills of the agent. Multiple values are separated by comma.", parseUniqueCommaList, []).option("--execute-agent <agent>", "The execute agent of the agent.", "codex").option("--root-branch-id <branchId>", "The root branch id of the agent. Defaults to DEFAULT_PANTHEON_ROOT_BRANCH_ID, then project root branch id.").option("--prototype-url <url>", "Role and skill definitions repo.", "https://github.com/pingcap-inc/pantheon-agents").option("--setup-script <slug>", "Setup script slug. CLI resolves it to setup-<slug>.sh.", parseSetupScriptSlug).action(async function() {
18103
+ const [name, role] = this.args;
18032
18104
  const options = this.opts();
18105
+ const resolvedProjectId = resolvePantheonProjectId(options.projectId);
18106
+ if (!resolvedProjectId) {
18107
+ console.error("project-id is required (--project-id or DEFAULT_PANTHEON_PROJECT_ID).");
18108
+ process$1.exitCode = 1;
18109
+ return;
18110
+ }
18111
+ const resolvedRootBranchId = resolvePantheonRootBranchId(options.rootBranchId);
18033
18112
  const requiredEnvVars = ["DATABASE_URL"];
18034
- if (options.bootstrap || !options.rootBranchId) requiredEnvVars.push("PANTHEON_API_KEY");
18113
+ if (!resolvedRootBranchId) requiredEnvVars.push("PANTHEON_API_KEY");
18035
18114
  if (!ensureEnv(requiredEnvVars)) return;
18036
18115
  await configAgent(name, {
18037
18116
  role,
18038
- projectId,
18039
- ...options
18117
+ projectId: resolvedProjectId,
18118
+ ...options,
18119
+ rootBranchId: resolvedRootBranchId,
18120
+ setupScript: options.setupScript ?? options.executeAgent
18040
18121
  });
18041
18122
  });
18042
18123
  }
@@ -18156,82 +18237,18 @@ function createLlmExplainCommand(version) {
18156
18237
  });
18157
18238
  }
18158
18239
 
18159
- //#endregion
18160
- //#region src/cli/utils/parse.ts
18161
- function parseCommaList(value) {
18162
- return value.split(",").map((item) => item.trim()).filter((item) => item !== "");
18163
- }
18164
- function parseUniqueCommaList(value) {
18165
- const items = parseCommaList(value);
18166
- const seen = /* @__PURE__ */ new Set();
18167
- const result = [];
18168
- for (const item of items) {
18169
- if (seen.has(item)) continue;
18170
- seen.add(item);
18171
- result.push(item);
18172
- }
18173
- return result;
18174
- }
18175
- function isLeapYear(year) {
18176
- return year % 4 === 0 && year % 100 !== 0 || year % 400 === 0;
18177
- }
18178
- function daysInMonth(year, month) {
18179
- switch (month) {
18180
- case 2: return isLeapYear(year) ? 29 : 28;
18181
- case 4:
18182
- case 6:
18183
- case 9:
18184
- case 11: return 30;
18185
- default: return 31;
18186
- }
18187
- }
18188
- function parseIsoTimestamp(value) {
18189
- const invalid = (detail) => ({
18190
- ok: false,
18191
- error: `Invalid timestamp: ${JSON.stringify(value)}. ` + (detail ? `${detail} ` : "") + "Expected ISO-8601 date-time with timezone, e.g. 2026-02-12T00:00:00Z."
18192
- });
18193
- const input = value.trim();
18194
- const match = /^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2})(?:\.(\d{1,3}))?(Z|([+-])(\d{2}):(\d{2}))$/.exec(input);
18195
- if (!match) return invalid();
18196
- const year = Number(match[1]);
18197
- const month = Number(match[2]);
18198
- const day = Number(match[3]);
18199
- const hour = Number(match[4]);
18200
- const minute = Number(match[5]);
18201
- const second = Number(match[6]);
18202
- const fraction = match[7];
18203
- if (month < 1 || month > 12) return invalid();
18204
- const dim = daysInMonth(year, month);
18205
- if (day < 1 || day > dim) return invalid("Day is out of range for month.");
18206
- if (hour < 0 || hour > 23) return invalid("Hour must be between 00 and 23.");
18207
- if (minute < 0 || minute > 59) return invalid("Minute must be between 00 and 59.");
18208
- if (second < 0 || second > 59) return invalid("Second must be between 00 and 59.");
18209
- const millis = fraction ? Number(fraction.padEnd(3, "0")) : 0;
18210
- if (!Number.isInteger(millis) || millis < 0 || millis > 999) return invalid("Fractional seconds must have 1-3 digits.");
18211
- const timezone = match[8];
18212
- let offsetMinutes = 0;
18213
- if (timezone !== "Z") {
18214
- const sign = match[9];
18215
- const offsetHours = Number(match[10]);
18216
- const offsetMins = Number(match[11]);
18217
- if (sign !== "+" && sign !== "-" || offsetHours < 0 || offsetHours > 23 || offsetMins < 0 || offsetMins > 59) return invalid("Timezone offset must be between -23:59 and +23:59.");
18218
- offsetMinutes = (sign === "+" ? 1 : -1) * (offsetHours * 60 + offsetMins);
18219
- }
18220
- const utcMillis = Date.UTC(year, month - 1, day, hour, minute, second, millis) - offsetMinutes * 6e4;
18221
- const date = new Date(utcMillis);
18222
- if (Number.isNaN(date.getTime())) return invalid();
18223
- return {
18224
- ok: true,
18225
- value: date
18226
- };
18227
- }
18228
-
18229
18240
  //#endregion
18230
18241
  //#region src/cli/commands/reconfig.ts
18231
18242
  function createReconfigAgentCommand(version) {
18232
- return createCommand("reconfig").version(version).description("Re-bootstrap an existing agent configuration for a project").argument("<name>", "The name of the agent.").argument("<project-id>", "The project id of the agent.").option("--role <role>", "Override role for the agent.").option("--skills <skills>", "Override skills for the agent (comma-separated, replaces existing list).", parseUniqueCommaList).action(async function() {
18233
- const [name, projectId] = this.args;
18243
+ return createCommand("reconfig").version(version).description("Re-bootstrap an existing agent configuration for a project").argument("<name>", "The name of the agent.").option("--project-id <project-id>", "The project id of the agent. Defaults to DEFAULT_PANTHEON_PROJECT_ID.").option("--role <role>", "Override role for the agent.").option("--skills <skills>", "Override skills for the agent (comma-separated, replaces existing list).", parseUniqueCommaList).action(async function() {
18244
+ const [name] = this.args;
18234
18245
  const options = this.opts();
18246
+ const projectId = resolvePantheonProjectId(options.projectId);
18247
+ if (!projectId) {
18248
+ console.error("project-id is required (--project-id or DEFAULT_PANTHEON_PROJECT_ID).");
18249
+ process$1.exitCode = 1;
18250
+ return;
18251
+ }
18235
18252
  if (!ensureEnv(["DATABASE_URL", "PANTHEON_API_KEY"])) return;
18236
18253
  const resolvedRole = options.role?.trim();
18237
18254
  if (options.role != null && !resolvedRole) {
@@ -20843,6 +20860,7 @@ function agentConfigToDTO(config) {
20843
20860
  role: config.role,
20844
20861
  skills: config.skills,
20845
20862
  prototype_url: config.prototype_url,
20863
+ setup_script: config.setup_script,
20846
20864
  execute_agent: config.execute_agent
20847
20865
  };
20848
20866
  }
@@ -20856,67 +20874,16 @@ function assertPantheonApiKeyConfigured() {
20856
20874
  message: "Missing PANTHEON_API_KEY. This endpoint requires Pantheon API access."
20857
20875
  });
20858
20876
  }
20859
- async function getBranchOutputOrThrow(options) {
20860
- let state;
20861
- try {
20862
- state = await options.getPantheonBranchFn({
20863
- projectId: options.projectId,
20864
- branchId: options.branchId,
20865
- getOutputIfFinished: true,
20866
- manifestingAsSucceed: true
20867
- });
20868
- } catch (error) {
20869
- throw new ApiError({
20870
- status: 502,
20871
- code: "pantheon_api_error",
20872
- message: `Failed to fetch Pantheon branch output for ${options.branchId}.`,
20873
- details: {
20874
- project_id: options.projectId,
20875
- branch_id: options.branchId,
20876
- error: getErrorMessage$2(error)
20877
- }
20878
- });
20879
- }
20880
- if (state.state === "succeed") return state.output ?? "";
20881
- if (state.state === "failed") throw new ApiError({
20882
- status: 502,
20883
- code: "pantheon_branch_failed",
20884
- message: `Pantheon branch ${options.branchId} failed.`,
20885
- details: {
20886
- project_id: options.projectId,
20887
- branch_id: options.branchId,
20888
- error: state.error
20889
- }
20890
- });
20891
- throw new ApiError({
20892
- status: 502,
20893
- code: "pantheon_branch_not_ready",
20894
- message: `Pantheon branch ${options.branchId} is not finished yet.`,
20895
- details: {
20896
- project_id: options.projectId,
20897
- branch_id: options.branchId
20898
- }
20899
- });
20900
- }
20901
- async function getAgentConfigWithBootstrapOutput(options) {
20902
- assertPantheonApiKeyConfigured();
20877
+ async function getAgentConfig(options) {
20903
20878
  const config = await options.provider.getAgentConfig(options.projectId);
20904
20879
  if (!config) throw new ApiError({
20905
20880
  status: 404,
20906
20881
  code: "config_not_found",
20907
20882
  message: `No config found for agent ${options.agent} and project ${options.projectId}.`
20908
20883
  });
20909
- const bootstrapOutput = await getBranchOutputOrThrow({
20910
- projectId: config.project_id,
20911
- branchId: config.base_branch_id,
20912
- getPantheonBranchFn: options.getPantheonBranchFn ?? getPantheonBranch
20913
- });
20914
- return {
20915
- config: agentConfigToDTO(config),
20916
- bootstrap_output: bootstrapOutput
20917
- };
20884
+ return { config: agentConfigToDTO(config) };
20918
20885
  }
20919
- async function reconfigAgentConfigWithBootstrapOutput(options) {
20886
+ async function reconfigAgentConfig(options) {
20920
20887
  assertPantheonApiKeyConfigured();
20921
20888
  const getPantheonBranchFn = options.getPantheonBranchFn ?? getPantheonBranch;
20922
20889
  const wrappedGetPantheonBranch = (args) => getPantheonBranchFn({
@@ -20941,7 +20908,7 @@ async function reconfigAgentConfigWithBootstrapOutput(options) {
20941
20908
  code: "config_not_found",
20942
20909
  message
20943
20910
  });
20944
- if (message === "--role must be non-empty.") throw new ApiError({
20911
+ if (message === "--role must be non-empty." || message === "--setup-script must be non-empty.") throw new ApiError({
20945
20912
  status: 400,
20946
20913
  code: "validation_error",
20947
20914
  message
@@ -20966,8 +20933,7 @@ async function reconfigAgentConfigWithBootstrapOutput(options) {
20966
20933
  });
20967
20934
  return {
20968
20935
  previous_config: agentConfigToDTO(result.previousConfig),
20969
- config: agentConfigToDTO(updated),
20970
- bootstrap_output: result.bootstrapOutput
20936
+ config: agentConfigToDTO(updated)
20971
20937
  };
20972
20938
  }
20973
20939
 
@@ -21011,7 +20977,8 @@ const createTaskBodySchema = z$1.object({
21011
20977
  });
21012
20978
  const reconfigBodySchema = z$1.object({
21013
20979
  role: z$1.string().trim().min(1).optional(),
21014
- skills: z$1.array(z$1.string().trim().min(1)).optional()
20980
+ skills: z$1.array(z$1.string().trim().min(1)).optional(),
20981
+ setup_script: z$1.string().trim().min(1).optional()
21015
20982
  });
21016
20983
  function getAgentParam(value) {
21017
20984
  if (typeof value !== "string" || value.trim().length === 0) throw new ApiError({
@@ -21046,7 +21013,7 @@ function createApiRouter(options) {
21046
21013
  }));
21047
21014
  router.get("/agents/:agent/configs/:project_id", asyncHandler(async (req, res) => {
21048
21015
  const agent = getAgentParam(req.params.agent);
21049
- const result = await getAgentConfigWithBootstrapOutput({
21016
+ const result = await getAgentConfig({
21050
21017
  agent,
21051
21018
  projectId: getProjectIdParam(req.params.project_id),
21052
21019
  provider: new TaskListTidbProvider(agent, logger, { db })
@@ -21058,12 +21025,13 @@ function createApiRouter(options) {
21058
21025
  const projectId = getProjectIdParam(req.params.project_id);
21059
21026
  const body = reconfigBodySchema.parse(req.body);
21060
21027
  const provider = new TaskListTidbProvider(agent, logger, { db });
21061
- const result = await reconfigAgentConfigWithBootstrapOutput({
21028
+ const result = await reconfigAgentConfig({
21062
21029
  agent,
21063
21030
  reconfig: {
21064
21031
  projectId,
21065
21032
  role: body.role,
21066
- skills: body.skills
21033
+ skills: body.skills,
21034
+ setupScript: body.setup_script
21067
21035
  },
21068
21036
  provider
21069
21037
  });
@@ -30958,7 +30926,7 @@ function createAgentsMcpServer(options) {
30958
30926
  };
30959
30927
  });
30960
30928
  server.registerTool("configs.get", {
30961
- description: "Get an agent's config for a project, including the bootstrap branch output.",
30929
+ description: "Get an agent's config for a project.",
30962
30930
  inputSchema: {
30963
30931
  agent: z$1.string().min(1),
30964
30932
  project_id: z$1.string().min(1)
@@ -30969,7 +30937,7 @@ function createAgentsMcpServer(options) {
30969
30937
  type: "text",
30970
30938
  text: "OK"
30971
30939
  }],
30972
- structuredContent: await getAgentConfigWithBootstrapOutput({
30940
+ structuredContent: await getAgentConfig({
30973
30941
  agent,
30974
30942
  projectId: project_id,
30975
30943
  provider: new TaskListTidbProvider(agent, options.logger, { db: options.db })
@@ -30982,21 +30950,23 @@ function createAgentsMcpServer(options) {
30982
30950
  agent: z$1.string().min(1),
30983
30951
  project_id: z$1.string().min(1),
30984
30952
  role: z$1.string().trim().min(1).optional(),
30985
- skills: z$1.array(z$1.string().trim().min(1)).optional()
30953
+ skills: z$1.array(z$1.string().trim().min(1)).optional(),
30954
+ setup_script: z$1.string().trim().min(1).optional()
30986
30955
  }
30987
- }, async ({ agent, project_id, role, skills }) => {
30956
+ }, async ({ agent, project_id, role, skills, setup_script }) => {
30988
30957
  const provider = new TaskListTidbProvider(agent, options.logger, { db: options.db });
30989
30958
  return {
30990
30959
  content: [{
30991
30960
  type: "text",
30992
30961
  text: "OK"
30993
30962
  }],
30994
- structuredContent: await reconfigAgentConfigWithBootstrapOutput({
30963
+ structuredContent: await reconfigAgentConfig({
30995
30964
  agent,
30996
30965
  reconfig: {
30997
30966
  projectId: project_id,
30998
30967
  role,
30999
- skills
30968
+ skills,
30969
+ setupScript: setup_script
31000
30970
  },
31001
30971
  provider
31002
30972
  })
@@ -31121,9 +31091,10 @@ function createServerCommand(version) {
31121
31091
  //#endregion
31122
31092
  //#region src/cli/commands/show-config.ts
31123
31093
  function createShowConfigCommand(version) {
31124
- return createCommand("show-config").version(version).description("Show agent config for a project").argument("<name>", "The name of the agent.").argument("[project-id]", "The project id.").option("--json", "Output config as JSON.").action(async function() {
31125
- const [name, projectId] = this.args;
31094
+ return createCommand("show-config").version(version).description("Show agent config for a project").argument("<name>", "The name of the agent.").option("--project-id <project-id>", "The project id. Defaults to DEFAULT_PANTHEON_PROJECT_ID.").option("--json", "Output config as JSON.").action(async function() {
31095
+ const [name] = this.args;
31126
31096
  const options = this.opts();
31097
+ const projectId = resolvePantheonProjectId(options.projectId);
31127
31098
  if (!ensureEnv(["DATABASE_URL"])) return;
31128
31099
  if (projectId) {
31129
31100
  const config = await showAgentConfig(name, projectId);
@@ -31143,6 +31114,7 @@ function createShowConfigCommand(version) {
31143
31114
  role: config.role,
31144
31115
  execute_agent: config.execute_agent,
31145
31116
  prototype_url: config.prototype_url,
31117
+ setup_script: config.setup_script,
31146
31118
  skills: Array.isArray(config.skills) ? config.skills.join(", ") : String(config.skills)
31147
31119
  }]);
31148
31120
  return;
@@ -31164,6 +31136,7 @@ function createShowConfigCommand(version) {
31164
31136
  role: config.role,
31165
31137
  execute_agent: config.execute_agent,
31166
31138
  prototype_url: config.prototype_url,
31139
+ setup_script: config.setup_script,
31167
31140
  skills: Array.isArray(config.skills) ? config.skills.join(", ") : String(config.skills)
31168
31141
  })));
31169
31142
  });
@@ -31370,8 +31343,8 @@ const mcpToolCallItemSchema = z$1.object({
31370
31343
  result: z$1.object({
31371
31344
  content: z$1.array(z$1.any()),
31372
31345
  structured_content: z$1.any()
31373
- }).optional(),
31374
- error: z$1.object({ message: z$1.string() }).optional(),
31346
+ }).nullable().transform((v) => v ?? void 0).optional(),
31347
+ error: z$1.object({ message: z$1.string() }).nullable().transform((v) => v ?? void 0).optional(),
31375
31348
  status: z$1.enum([
31376
31349
  "in_progress",
31377
31350
  "completed",
@@ -31760,16 +31733,40 @@ function createCodexNormalizer(options = {}) {
31760
31733
 
31761
31734
  //#endregion
31762
31735
  //#region ../agent-stream-parser/src/claude.ts
31763
- const topLevelSchema = z$1.object({ type: z$1.string() }).passthrough();
31764
- const streamEventSchema = z$1.object({
31765
- type: z$1.string(),
31766
- index: z$1.number().optional()
31767
- }).passthrough();
31736
+ function toContentBlockEnvelope(value) {
31737
+ if (!isRecord(value) || typeof value.type !== "string") return null;
31738
+ return {
31739
+ type: value.type,
31740
+ text: typeof value.text === "string" ? value.text : void 0,
31741
+ id: typeof value.id === "string" ? value.id : void 0,
31742
+ name: typeof value.name === "string" ? value.name : void 0,
31743
+ input: value.input
31744
+ };
31745
+ }
31746
+ function toToolResultEnvelope(value) {
31747
+ if (!isRecord(value) || value.type !== "tool_result") return null;
31748
+ return {
31749
+ type: "tool_result",
31750
+ tool_use_id: typeof value.tool_use_id === "string" ? value.tool_use_id : void 0,
31751
+ content: value.content,
31752
+ is_error: typeof value.is_error === "boolean" ? value.is_error : void 0
31753
+ };
31754
+ }
31755
+ function getMessageContentBlocks(value) {
31756
+ if (!isRecord(value) || !Array.isArray(value.content)) return [];
31757
+ return value.content;
31758
+ }
31759
+ function isToolUseLikeType(type) {
31760
+ return type === "tool_use" || type === "mcp_tool_use" || type === "server_tool_use";
31761
+ }
31768
31762
  function createClaudeCodeNormalizer(options = {}) {
31769
31763
  const includeMetaEvents = options.includeMetaEvents ?? false;
31770
31764
  const textBlocks = /* @__PURE__ */ new Map();
31771
31765
  const toolBlocks = /* @__PURE__ */ new Map();
31772
- let sawStreamEvents = false;
31766
+ const assistantTextBlocks = /* @__PURE__ */ new Map();
31767
+ const knownToolCallIds = /* @__PURE__ */ new Set();
31768
+ let currentAssistantMessageId;
31769
+ let assistantFallbackMessageSeq = 0;
31773
31770
  function maybeMeta(event) {
31774
31771
  if (!includeMetaEvents) return [];
31775
31772
  return [{
@@ -31777,145 +31774,50 @@ function createClaudeCodeNormalizer(options = {}) {
31777
31774
  data: event
31778
31775
  }];
31779
31776
  }
31780
- function ensureTextBlock(index) {
31781
- const existing = textBlocks.get(index);
31782
- if (existing?.open) return [];
31783
- const id = existing?.id ?? `claude-text-${index}`;
31784
- textBlocks.set(index, {
31785
- id,
31786
- open: true
31787
- });
31788
- return [{
31789
- type: "text-start",
31790
- id
31791
- }];
31792
- }
31793
- function ensureToolBlock(index, block) {
31794
- const existing = toolBlocks.get(index);
31795
- if (existing?.open) return [];
31796
- const toolCallId = block?.id ?? existing?.toolCallId ?? `claude-tool-${index}`;
31797
- const toolName = block?.name ?? existing?.toolName ?? "tool";
31798
- toolBlocks.set(index, {
31799
- toolCallId,
31800
- toolName,
31801
- json: "",
31802
- open: true
31803
- });
31804
- return [{
31805
- type: "tool-input-start",
31806
- toolCallId,
31807
- toolName,
31808
- dynamic: true
31809
- }];
31810
- }
31811
- function handleStreamEvent(event) {
31812
- if (!isRecord(event) || typeof event.type !== "string") return [];
31777
+ function handleAssistantMessage(message) {
31778
+ const resolvedMessageId = getAssistantMessageId(message);
31779
+ if (!resolvedMessageId) return [];
31813
31780
  const out = [];
31814
- const index = typeof event.index === "number" ? event.index : void 0;
31815
- switch (event.type) {
31816
- case "content_block_start": {
31817
- if (index == null) return [];
31818
- const block = event.content_block;
31819
- if (!isRecord(block) || typeof block.type !== "string") return [];
31820
- sawStreamEvents = true;
31821
- if (block.type === "text") out.push(...ensureTextBlock(index));
31822
- else if (block.type === "tool_use") out.push(...ensureToolBlock(index, {
31823
- id: typeof block.id === "string" ? block.id : void 0,
31824
- name: typeof block.name === "string" ? block.name : void 0
31825
- }));
31826
- return out;
31827
- }
31828
- case "content_block_delta": {
31829
- if (index == null) return [];
31830
- const delta = event.delta;
31831
- if (!isRecord(delta) || typeof delta.type !== "string") return [];
31832
- sawStreamEvents = true;
31833
- if (delta.type === "text_delta") {
31834
- const text = typeof delta.text === "string" ? delta.text : "";
31835
- if (!text) return [];
31836
- out.push(...ensureTextBlock(index));
31837
- const id = textBlocks.get(index).id;
31838
- out.push({
31839
- type: "text-delta",
31840
- id,
31841
- delta: text
31842
- });
31843
- return out;
31844
- }
31845
- if (delta.type === "input_json_delta") {
31846
- const partialJson = typeof delta.partial_json === "string" ? delta.partial_json : "";
31847
- if (!partialJson) return [];
31848
- out.push(...ensureToolBlock(index));
31849
- const tool = toolBlocks.get(index);
31850
- tool.json += partialJson;
31851
- out.push({
31852
- type: "tool-input-delta",
31853
- toolCallId: tool.toolCallId,
31854
- inputTextDelta: partialJson
31855
- });
31856
- return out;
31857
- }
31858
- return [];
31859
- }
31860
- case "content_block_stop": {
31861
- if (index == null) return [];
31862
- sawStreamEvents = true;
31863
- const text = textBlocks.get(index);
31864
- if (text?.open) {
31865
- text.open = false;
31781
+ if (currentAssistantMessageId && currentAssistantMessageId !== resolvedMessageId) out.push(...closeAssistantTextBlocksForMessage(currentAssistantMessageId));
31782
+ currentAssistantMessageId = resolvedMessageId;
31783
+ const blocks = getMessageContentBlocks(message);
31784
+ const seenTextBlockKeys = /* @__PURE__ */ new Set();
31785
+ blocks.forEach((block, index) => {
31786
+ const contentBlock = toContentBlockEnvelope(block);
31787
+ if (!contentBlock) return;
31788
+ if (contentBlock.type === "text") {
31789
+ const key = `${resolvedMessageId}:${index}`;
31790
+ seenTextBlockKeys.add(key);
31791
+ let state = assistantTextBlocks.get(key);
31792
+ if (!state || !state.open) {
31793
+ state = {
31794
+ id: state?.id ?? `claude-assistant-text-${resolvedMessageId}-${index}`,
31795
+ messageId: resolvedMessageId,
31796
+ lastText: state?.lastText ?? "",
31797
+ open: true
31798
+ };
31799
+ assistantTextBlocks.set(key, state);
31866
31800
  out.push({
31867
- type: "text-end",
31868
- id: text.id
31801
+ type: "text-start",
31802
+ id: state.id
31869
31803
  });
31870
31804
  }
31871
- const tool = toolBlocks.get(index);
31872
- if (tool?.open) {
31873
- tool.open = false;
31874
- let input = tool.json ? tool.json : {};
31875
- if (typeof input === "string") {
31876
- const parsed = safeJsonParse(input);
31877
- if (parsed.ok) input = parsed.value;
31878
- }
31805
+ const fullText = contentBlock.text ?? "";
31806
+ const { delta, reset } = computeAppendDelta(state.lastText, fullText);
31807
+ if (delta) {
31879
31808
  out.push({
31880
- type: "tool-input-available",
31881
- toolCallId: tool.toolCallId,
31882
- toolName: tool.toolName,
31883
- input,
31884
- dynamic: true
31809
+ type: "text-delta",
31810
+ id: state.id,
31811
+ delta
31885
31812
  });
31813
+ state.lastText = reset ? fullText : state.lastText + delta;
31886
31814
  }
31887
- return out;
31888
- }
31889
- default: return [];
31890
- }
31891
- }
31892
- function handleAssistantMessage(message) {
31893
- if (sawStreamEvents) return [];
31894
- const blocks = Array.isArray(message.content) ? message.content : [];
31895
- const out = [];
31896
- blocks.forEach((block, index) => {
31897
- if (!isRecord(block) || typeof block.type !== "string") return;
31898
- if (block.type === "text") {
31899
- const id = `claude-assistant-text-${index}`;
31900
- const text = typeof block.text === "string" ? block.text : "";
31901
- out.push({
31902
- type: "text-start",
31903
- id
31904
- });
31905
- if (text) out.push({
31906
- type: "text-delta",
31907
- id,
31908
- delta: text
31909
- });
31910
- out.push({
31911
- type: "text-end",
31912
- id
31913
- });
31914
31815
  }
31915
- if (block.type === "tool_use") {
31916
- const toolCallId = typeof block.id === "string" ? block.id : `claude-assistant-tool-${index}`;
31917
- const toolName = typeof block.name === "string" ? block.name : "tool";
31918
- const input = block.input;
31816
+ if (isToolUseLikeType(contentBlock.type)) {
31817
+ const toolCallId = contentBlock.id ?? `claude-assistant-tool-${index}`;
31818
+ const toolName = contentBlock.name ?? "tool";
31819
+ const input = isRecord(contentBlock.input) ? contentBlock.input : contentBlock.input;
31820
+ knownToolCallIds.add(toolCallId);
31919
31821
  out.push({
31920
31822
  type: "tool-input-available",
31921
31823
  toolCallId,
@@ -31925,6 +31827,33 @@ function createClaudeCodeNormalizer(options = {}) {
31925
31827
  });
31926
31828
  }
31927
31829
  });
31830
+ for (const [key, state] of assistantTextBlocks) {
31831
+ if (state.messageId !== resolvedMessageId) continue;
31832
+ if (!state.open) continue;
31833
+ if (seenTextBlockKeys.has(key)) continue;
31834
+ state.open = false;
31835
+ out.push({
31836
+ type: "text-end",
31837
+ id: state.id
31838
+ });
31839
+ }
31840
+ return out;
31841
+ }
31842
+ function getAssistantMessageId(message) {
31843
+ if (isRecord(message) && typeof message.id === "string") return message.id;
31844
+ assistantFallbackMessageSeq += 1;
31845
+ return `claude-assistant-fallback-${assistantFallbackMessageSeq}`;
31846
+ }
31847
+ function closeAssistantTextBlocksForMessage(messageId) {
31848
+ const out = [];
31849
+ for (const state of assistantTextBlocks.values()) {
31850
+ if (state.messageId !== messageId || !state.open) continue;
31851
+ state.open = false;
31852
+ out.push({
31853
+ type: "text-end",
31854
+ id: state.id
31855
+ });
31856
+ }
31928
31857
  return out;
31929
31858
  }
31930
31859
  function formatToolResultOutput(content, toolUseResult) {
@@ -31948,14 +31877,26 @@ function createClaudeCodeNormalizer(options = {}) {
31948
31877
  return "Tool execution failed";
31949
31878
  }
31950
31879
  function handleUserMessage(message, toolUseResult) {
31951
- const blocks = Array.isArray(message.content) ? message.content : [];
31880
+ const blocks = getMessageContentBlocks(message);
31952
31881
  const out = [];
31953
31882
  blocks.forEach((block, index) => {
31954
- if (!isRecord(block) || block.type !== "tool_result") return;
31955
- const toolCallId = typeof block.tool_use_id === "string" ? block.tool_use_id : `claude-tool-result-${index}`;
31956
- const content = block.content;
31883
+ const toolResult = toToolResultEnvelope(block);
31884
+ if (!toolResult) return;
31885
+ const toolCallId = toolResult.tool_use_id ?? `claude-tool-result-${index}`;
31886
+ const content = toolResult.content;
31957
31887
  const interrupted = isRecord(toolUseResult) && toolUseResult.interrupted === true;
31958
- if (block.is_error === true || interrupted) {
31888
+ const isError = toolResult.is_error === true || interrupted;
31889
+ if (!knownToolCallIds.has(toolCallId)) {
31890
+ knownToolCallIds.add(toolCallId);
31891
+ out.push({
31892
+ type: "tool-input-available",
31893
+ toolCallId,
31894
+ toolName: "tool",
31895
+ input: {},
31896
+ dynamic: true
31897
+ });
31898
+ }
31899
+ if (isError) {
31959
31900
  out.push({
31960
31901
  type: "tool-output-error",
31961
31902
  toolCallId,
@@ -31974,12 +31915,11 @@ function createClaudeCodeNormalizer(options = {}) {
31974
31915
  return out;
31975
31916
  }
31976
31917
  function push(chunk) {
31977
- const parsed = topLevelSchema.safeParse(chunk);
31978
- if (!parsed.success) return {
31918
+ if (!isRecord(chunk) || typeof chunk.type !== "string") return {
31979
31919
  recognized: false,
31980
31920
  chunks: []
31981
31921
  };
31982
- const msg = parsed.data;
31922
+ const msg = chunk;
31983
31923
  const out = [];
31984
31924
  switch (msg.type) {
31985
31925
  case "system": {
@@ -32003,39 +31943,28 @@ function createClaudeCodeNormalizer(options = {}) {
32003
31943
  chunks: out
32004
31944
  };
32005
31945
  }
32006
- case "stream_event": {
32007
- const event = streamEventSchema.safeParse(msg.event);
32008
- if (!event.success) return {
32009
- recognized: false,
32010
- chunks: []
32011
- };
32012
- return {
32013
- recognized: true,
32014
- chunks: handleStreamEvent(event.data)
32015
- };
32016
- }
32017
- case "assistant": {
32018
- const message = msg.message;
32019
- if (!isRecord(message)) return {
31946
+ case "stream_event": return {
31947
+ recognized: true,
31948
+ chunks: []
31949
+ };
31950
+ case "assistant":
31951
+ if (!isRecord(msg.message)) return {
32020
31952
  recognized: false,
32021
31953
  chunks: []
32022
31954
  };
32023
31955
  return {
32024
31956
  recognized: true,
32025
- chunks: handleAssistantMessage(message)
31957
+ chunks: handleAssistantMessage(msg.message)
32026
31958
  };
32027
- }
32028
- case "user": {
32029
- const message = msg.message;
32030
- if (!isRecord(message)) return {
31959
+ case "user":
31960
+ if (!isRecord(msg.message)) return {
32031
31961
  recognized: false,
32032
31962
  chunks: []
32033
31963
  };
32034
31964
  return {
32035
31965
  recognized: true,
32036
- chunks: handleUserMessage(message, msg.tool_use_result)
31966
+ chunks: handleUserMessage(msg.message, msg.tool_use_result)
32037
31967
  };
32038
- }
32039
31968
  case "result": return {
32040
31969
  recognized: true,
32041
31970
  chunks: maybeMeta({
@@ -32071,6 +32000,14 @@ function createClaudeCodeNormalizer(options = {}) {
32071
32000
  dynamic: true
32072
32001
  });
32073
32002
  }
32003
+ for (const state of assistantTextBlocks.values()) {
32004
+ if (!state.open) continue;
32005
+ state.open = false;
32006
+ out.push({
32007
+ type: "text-end",
32008
+ id: state.id
32009
+ });
32010
+ }
32074
32011
  return out;
32075
32012
  }
32076
32013
  return {
@@ -32198,7 +32135,7 @@ function commandBasename(value) {
32198
32135
  const lastSlash = value.lastIndexOf("/");
32199
32136
  return lastSlash === -1 ? value : value.slice(lastSlash + 1);
32200
32137
  }
32201
- function parseShellEntries(line) {
32138
+ function parseShellEntries$1(line) {
32202
32139
  try {
32203
32140
  return parse(line);
32204
32141
  } catch {
@@ -32241,7 +32178,7 @@ function pickLastNonOptionArg(tokens) {
32241
32178
  return values.length > 0 ? values[values.length - 1] : void 0;
32242
32179
  }
32243
32180
  function parseExploredItem(line) {
32244
- const entries = parseShellEntries(normalizeCommand(line));
32181
+ const entries = parseShellEntries$1(normalizeCommand(line));
32245
32182
  if (!entries) return void 0;
32246
32183
  const tokens = takeMainTokens(entries);
32247
32184
  if (tokens.length === 0) return void 0;
@@ -32320,128 +32257,8 @@ function formatCommandExecutionDisplayCommand(command) {
32320
32257
  }
32321
32258
 
32322
32259
  //#endregion
32323
- //#region src/cli/commands/watch.ts
32324
- const PANTHEON_BASE_URL = "https://pantheon-ai.tidb.ai";
32325
- function parseListWidth(value) {
32326
- const width = Number(value);
32327
- if (!Number.isInteger(width) || width <= 0) throw new InvalidArgumentError("list-width requires a positive integer, e.g. 32");
32328
- return width;
32329
- }
32330
- function ensureEnvOrExit(keys) {
32331
- const missing = keys.filter((key) => !process.env[key]);
32332
- if (missing.length === 0) return;
32333
- for (const key of missing) console.error(`${key} environment variable is not set.`);
32334
- process.exit(1);
32335
- }
32336
- function toStreamSourceFromExecuteAgent(executeAgent) {
32337
- if (!executeAgent) return void 0;
32338
- const normalized = executeAgent.toLowerCase();
32339
- if (normalized.includes("claude")) return "claude-code";
32340
- if (normalized.includes("codex")) return "codex";
32341
- }
32342
- function detectStreamSourceFromRawPayload(payload) {
32343
- if (payload.includes("\"type\":\"thread.") || payload.includes("\"type\":\"turn.") || payload.includes("\"type\":\"item.")) return "codex";
32344
- if (payload.includes("\"type\":\"message_start\"") || payload.includes("\"type\":\"message_delta\"") || payload.includes("\"type\":\"message_stop\"") || payload.includes("\"type\":\"content_block_start\"") || payload.includes("\"type\":\"content_block_delta\"") || payload.includes("\"type\":\"content_block_stop\"")) return "claude-code";
32345
- }
32346
- async function getAgentExecuteAgentMap(db, targets) {
32347
- const agents = Array.from(new Set(targets.map((t) => t.agent)));
32348
- if (agents.length === 0) return /* @__PURE__ */ new Map();
32349
- const configs = await db.selectFrom("agent_project_config").select([
32350
- "agent",
32351
- "project_id",
32352
- "execute_agent"
32353
- ]).where("agent", "in", agents).execute();
32354
- const map = /* @__PURE__ */ new Map();
32355
- for (const row of configs) map.set(`${row.agent}|${row.project_id}`, row.execute_agent);
32356
- return map;
32357
- }
32358
- async function loadTasksByIds(db, ids) {
32359
- if (ids.length === 0) return [];
32360
- const rows = await db.selectFrom("task").selectAll().where("id", "in", ids).execute();
32361
- const out = [];
32362
- for (const row of rows) {
32363
- const parsed = taskItemSchema.safeParse(row);
32364
- if (!parsed.success) {
32365
- console.error(`Failed to parse task row id=${row.id} agent=${row.agent} status=${row.status}: ${parsed.error.message}`);
32366
- continue;
32367
- }
32368
- out.push({
32369
- agent: row.agent,
32370
- task: parsed.data
32371
- });
32372
- }
32373
- return out;
32374
- }
32375
- async function loadTasksForAgent(db, agent) {
32376
- const rows = await db.selectFrom("task").selectAll().where("agent", "=", agent).execute();
32377
- const out = [];
32378
- for (const row of rows) {
32379
- const parsed = taskItemSchema.safeParse(row);
32380
- if (!parsed.success) continue;
32381
- out.push(parsed.data);
32382
- }
32383
- return out;
32384
- }
32385
- async function loadAllTasks(db, limit) {
32386
- const rows = await db.selectFrom("task").selectAll().orderBy("queued_at", "desc").limit(limit).execute();
32387
- const out = [];
32388
- for (const row of rows) {
32389
- const parsed = taskItemSchema.safeParse(row);
32390
- if (!parsed.success) continue;
32391
- out.push({
32392
- agent: row.agent,
32393
- task: parsed.data
32394
- });
32395
- }
32396
- return out;
32397
- }
32398
- async function resolveWatchTargets(options) {
32399
- const warnings = [];
32400
- let selected = [];
32401
- if (options.taskIds.length > 0) {
32402
- const uniqueTaskIds = Array.from(new Set(options.taskIds));
32403
- const found = await loadTasksByIds(options.db, uniqueTaskIds);
32404
- const foundIds = new Set(found.map((t) => t.task.id));
32405
- const missing = uniqueTaskIds.filter((id) => !foundIds.has(id));
32406
- if (missing.length > 0) warnings.push(`Requested task IDs not found: ${missing.join(", ")}`);
32407
- const byId = new Map(found.map((t) => [t.task.id, t]));
32408
- selected = uniqueTaskIds.map((id) => byId.get(id)).filter(Boolean);
32409
- if (selected.length === 0) warnings.push("No requested tasks found; falling back to default selection.");
32410
- } else if (options.agentNames.length > 0) {
32411
- const uniqueAgents = Array.from(new Set(options.agentNames));
32412
- if (uniqueAgents.length === 1) {
32413
- selected = selectSingleAgentWatchTasks(await loadTasksForAgent(options.db, uniqueAgents[0]), { finishedLimit: 3 }).map((task) => ({
32414
- agent: uniqueAgents[0],
32415
- task
32416
- }));
32417
- if (selected.length === 0) warnings.push(`No tasks found for agent ${uniqueAgents[0]}; falling back to default selection.`);
32418
- } else {
32419
- selected = selectMultiAgentWatchTasks(await Promise.all(uniqueAgents.map(async (agent) => {
32420
- return (await loadTasksForAgent(options.db, agent)).map((task) => ({
32421
- agent,
32422
- task
32423
- }));
32424
- })).then((groups) => groups.flat()), uniqueAgents);
32425
- const foundAgents = new Set(selected.map((t) => t.agent));
32426
- const missing = uniqueAgents.filter((agent) => !foundAgents.has(agent));
32427
- if (missing.length > 0) warnings.push(`No tasks found for agent(s): ${missing.join(", ")}`);
32428
- if (selected.length === 0) warnings.push("No requested agent tasks found; falling back to default selection.");
32429
- }
32430
- } else warnings.push("No targets specified; selecting up to 3 agents automatically.");
32431
- if (selected.length === 0) selected = selectFallbackWatchTasks(await loadAllTasks(options.db, 500), { agentLimit: 3 });
32432
- const executeAgentMap = await getAgentExecuteAgentMap(options.db, selected);
32433
- return {
32434
- targets: selected.map((entry) => ({
32435
- ...entry,
32436
- executeAgent: executeAgentMap.get(`${entry.agent}|${entry.task.project_id}`)
32437
- })),
32438
- warnings
32439
- };
32440
- }
32441
- function makeStreamUrl(streamId) {
32442
- return `${PANTHEON_BASE_URL}/ai_stream_proxy/v2/streams/${encodeURIComponent(streamId)}/stream?format=opaque-stream-json`;
32443
- }
32444
- const ANSI = {
32260
+ //#region src/cli/commands/watch/log-output.ts
32261
+ const ANSI$1 = {
32445
32262
  reset: "\x1B[0m",
32446
32263
  bold: "\x1B[1m",
32447
32264
  dim: "\x1B[2m",
@@ -32451,67 +32268,280 @@ const ANSI = {
32451
32268
  cyan: "\x1B[36m",
32452
32269
  gray: "\x1B[90m"
32453
32270
  };
32454
- function useAnsiStyles() {
32455
- if (!process.stdout.isTTY) return false;
32456
- if (process.env.NO_COLOR != null) return false;
32457
- return true;
32458
- }
32459
- function sgrWrap(text, code, enabled) {
32460
- return enabled ? `${code}${text}${ANSI.reset}` : text;
32271
+ function sgrWrap$1(text, code, enabled) {
32272
+ return enabled ? `${code}${text}${ANSI$1.reset}` : text;
32461
32273
  }
32462
- function bold(text, enabled) {
32463
- return sgrWrap(text, ANSI.bold, enabled);
32274
+ function bold$1(text, enabled) {
32275
+ return sgrWrap$1(text, ANSI$1.bold, enabled);
32464
32276
  }
32465
32277
  function dim(text, enabled) {
32466
- return sgrWrap(text, ANSI.dim, enabled);
32278
+ return sgrWrap$1(text, ANSI$1.dim, enabled);
32467
32279
  }
32468
32280
  function gray(text, enabled) {
32469
- return sgrWrap(text, ANSI.gray, enabled);
32281
+ return sgrWrap$1(text, ANSI$1.gray, enabled);
32470
32282
  }
32471
32283
  function red(text, enabled) {
32472
- return sgrWrap(text, ANSI.red, enabled);
32284
+ return sgrWrap$1(text, ANSI$1.red, enabled);
32473
32285
  }
32474
32286
  function green(text, enabled) {
32475
- return sgrWrap(text, ANSI.green, enabled);
32287
+ return sgrWrap$1(text, ANSI$1.green, enabled);
32476
32288
  }
32477
32289
  function yellow(text, enabled) {
32478
- return sgrWrap(text, ANSI.yellow, enabled);
32290
+ return sgrWrap$1(text, ANSI$1.yellow, enabled);
32479
32291
  }
32480
32292
  function cyan(text, enabled) {
32481
- return sgrWrap(text, ANSI.cyan, enabled);
32293
+ return sgrWrap$1(text, ANSI$1.cyan, enabled);
32482
32294
  }
32483
- function dimGray(text, enabled) {
32484
- return sgrWrap(text, `${ANSI.dim}${ANSI.gray}`, enabled);
32295
+ function dimGray$1(text, enabled) {
32296
+ return sgrWrap$1(text, `${ANSI$1.dim}${ANSI$1.gray}`, enabled);
32485
32297
  }
32486
32298
  function kv(label, value, style) {
32487
32299
  return `${dim(`${label}:`, style)} ${value}`;
32488
32300
  }
32301
+ function getCommandExecutionRawCommand(tool) {
32302
+ const title = typeof tool.title === "string" ? tool.title : "";
32303
+ const inputCommand = typeof tool.input?.command === "string" ? tool.input.command : "";
32304
+ return title || inputCommand || void 0;
32305
+ }
32489
32306
  function getCommandExecutionDisplayCommand(tool) {
32490
32307
  const raw = getCommandExecutionRawCommand(tool);
32491
32308
  if (!raw) return void 0;
32492
32309
  return formatCommandExecutionDisplayCommand(raw) || void 0;
32493
32310
  }
32494
- function colorizeCommandProgram(display, style) {
32495
- const match = display.match(/^(\s*)(\S+)([\s\S]*)$/);
32496
- if (!match) return display;
32497
- const [, leading, program, rest] = match;
32498
- return `${leading}${cyan(program ?? "", style)}${rest ?? ""}`;
32311
+ function isCommandLikeTool(toolName) {
32312
+ if (!toolName) return false;
32313
+ return toolName === "command_execution" || toolName.toLowerCase() === "bash";
32314
+ }
32315
+ function isClaudeExplorationTool(toolName) {
32316
+ if (!toolName) return false;
32317
+ const normalized = toolName.toLowerCase();
32318
+ return normalized === "read" || normalized === "glob" || normalized === "grep";
32319
+ }
32320
+ function isWriteTool(toolName) {
32321
+ if (!toolName) return false;
32322
+ return toolName.toLowerCase() === "write";
32323
+ }
32324
+ function isEditTool(toolName) {
32325
+ if (!toolName) return false;
32326
+ return toolName.toLowerCase() === "edit";
32327
+ }
32328
+ function toRecord(value) {
32329
+ return typeof value === "object" && value !== null ? value : void 0;
32330
+ }
32331
+ function toStringValue(value) {
32332
+ return typeof value === "string" ? value : void 0;
32333
+ }
32334
+ function toStringOrUndefined(value) {
32335
+ return typeof value === "string" && value.trim().length > 0 ? value : void 0;
32336
+ }
32337
+ function extractWritePath(input) {
32338
+ const record = toRecord(input);
32339
+ if (!record) return void 0;
32340
+ return toStringOrUndefined(record.file_path) ?? toStringOrUndefined(record.path) ?? toStringOrUndefined(record.filePath) ?? toStringOrUndefined(record.filepath) ?? toStringOrUndefined(record.filename);
32341
+ }
32342
+ function countContentLines(content) {
32343
+ const normalized = content.replaceAll("\r\n", "\n");
32344
+ if (!normalized) return 0;
32345
+ const lines = normalized.split("\n");
32346
+ if (lines.length > 0 && lines[lines.length - 1] === "") lines.pop();
32347
+ return lines.length;
32348
+ }
32349
+ function extractWriteAddedLineCount(input) {
32350
+ const record = toRecord(input);
32351
+ if (!record) return void 0;
32352
+ const content = toStringValue(record.content);
32353
+ if (content == null) return void 0;
32354
+ return countContentLines(content);
32355
+ }
32356
+ function extractEditAddedLineCount(input) {
32357
+ const record = toRecord(input);
32358
+ if (!record) return void 0;
32359
+ const oldText = toStringValue(record.old_string) ?? toStringValue(record.oldString) ?? "";
32360
+ const newText = toStringValue(record.new_string) ?? toStringValue(record.newString);
32361
+ if (newText == null) return void 0;
32362
+ const oldLines = countContentLines(oldText);
32363
+ const newLines = countContentLines(newText);
32364
+ return Math.max(0, newLines - oldLines);
32365
+ }
32366
+ function formatWriteToolLabel(tool, style) {
32367
+ const renderedName = bold$1(tool.toolName ?? "Write", style);
32368
+ const path = extractWritePath(tool.input);
32369
+ const addedLines = extractWriteAddedLineCount(tool.input);
32370
+ const chunks = [renderedName];
32371
+ if (path) chunks.push(path);
32372
+ if (addedLines != null) {
32373
+ const unit = addedLines === 1 ? "line" : "lines";
32374
+ chunks.push(`(${green(`+${addedLines} ${unit}`, style)})`);
32375
+ }
32376
+ if (!path && addedLines == null && tool.title) chunks.push(`: ${tool.title}`);
32377
+ return chunks.join(" ");
32378
+ }
32379
+ function formatEditToolLabel(tool, style) {
32380
+ const renderedName = bold$1(tool.toolName ?? "Edit", style);
32381
+ const path = extractWritePath(tool.input);
32382
+ const addedLines = extractEditAddedLineCount(tool.input);
32383
+ const chunks = [renderedName];
32384
+ if (path) chunks.push(path);
32385
+ if (addedLines != null) {
32386
+ const unit = addedLines === 1 ? "line" : "lines";
32387
+ chunks.push(`(${green(`+${addedLines} ${unit}`, style)})`);
32388
+ }
32389
+ if (!path && addedLines == null && tool.title) chunks.push(`: ${tool.title}`);
32390
+ return chunks.join(" ");
32391
+ }
32392
+ function stripOuterQuotes(token) {
32393
+ if (token.length < 2) return token;
32394
+ const first = token[0];
32395
+ const last = token[token.length - 1];
32396
+ if (first === "'" && last === "'" || first === "\"" && last === "\"") return token.slice(1, -1);
32397
+ return token;
32499
32398
  }
32500
- function getCommandExecutionRawCommand(tool) {
32501
- const title = typeof tool.title === "string" ? tool.title : "";
32502
- const inputCommand = typeof tool.input?.command === "string" ? tool.input.command : "";
32503
- return title || inputCommand || void 0;
32399
+ function isEnvAssignmentToken(token) {
32400
+ const normalized = stripOuterQuotes(token);
32401
+ const equalsAt = normalized.indexOf("=");
32402
+ if (equalsAt <= 0) return false;
32403
+ const key = normalized.slice(0, equalsAt);
32404
+ return /^[A-Za-z_][A-Za-z0-9_]*$/.test(key);
32405
+ }
32406
+ function isExecPrefixToken(token) {
32407
+ const normalized = stripOuterQuotes(token);
32408
+ return normalized === "env" || normalized === "sudo" || normalized === "builtin" || normalized === "time" || normalized === "nohup";
32409
+ }
32410
+ function parseShellEntries(line) {
32411
+ try {
32412
+ return parse(line);
32413
+ } catch {
32414
+ return;
32415
+ }
32416
+ }
32417
+ function colorizeExecToken(token, style) {
32418
+ if (!token) return token;
32419
+ const core = token;
32420
+ if (!core) return token;
32421
+ const lastSlash = core.lastIndexOf("/");
32422
+ if (lastSlash === -1 || lastSlash === core.length - 1) return bold$1(core, style);
32423
+ return `${core.slice(0, lastSlash + 1)}${bold$1(core.slice(lastSlash + 1), style)}`;
32424
+ }
32425
+ function isShellOperatorEntry(entry) {
32426
+ return typeof entry === "object" && entry != null && "op" in entry && typeof entry.op === "string";
32427
+ }
32428
+ function isShellCommentEntry(entry) {
32429
+ return typeof entry === "object" && entry != null && "comment" in entry && typeof entry.comment === "string";
32430
+ }
32431
+ function renderShellToken(rawToken, styledToken) {
32432
+ if (rawToken.length === 0) return `""`;
32433
+ if (!/\s/.test(rawToken)) return styledToken;
32434
+ return `"${styledToken.replaceAll("\\", "\\\\").replaceAll("\"", "\\\"")}"`;
32435
+ }
32436
+ function findCommandSubstitutionEnd(text, openAt) {
32437
+ let depth = 1;
32438
+ let i = openAt;
32439
+ let escaped = false;
32440
+ while (i < text.length) {
32441
+ const ch = text[i] ?? "";
32442
+ if (escaped) {
32443
+ escaped = false;
32444
+ i += 1;
32445
+ continue;
32446
+ }
32447
+ if (ch === "\\") {
32448
+ escaped = true;
32449
+ i += 1;
32450
+ continue;
32451
+ }
32452
+ if (ch === "$" && text[i + 1] === "(") {
32453
+ depth += 1;
32454
+ i += 2;
32455
+ continue;
32456
+ }
32457
+ if (ch === ")") {
32458
+ depth -= 1;
32459
+ if (depth === 0) return i;
32460
+ }
32461
+ i += 1;
32462
+ }
32463
+ return -1;
32464
+ }
32465
+ function colorizeCommandSubstitutions(token, style) {
32466
+ if (!token.includes("$(")) return token;
32467
+ let out = "";
32468
+ let i = 0;
32469
+ let cursor = 0;
32470
+ while (i < token.length) {
32471
+ if (token[i] === "$" && token[i + 1] === "(") {
32472
+ out += token.slice(cursor, i);
32473
+ const closeAt = findCommandSubstitutionEnd(token, i + 2);
32474
+ if (closeAt < 0) {
32475
+ out += token.slice(i);
32476
+ return out;
32477
+ }
32478
+ const innerColorized = colorizeCommandLine(token.slice(i + 2, closeAt), style);
32479
+ out += `$(${innerColorized})`;
32480
+ i = closeAt + 1;
32481
+ cursor = i;
32482
+ continue;
32483
+ }
32484
+ i += 1;
32485
+ }
32486
+ out += token.slice(cursor);
32487
+ return out;
32488
+ }
32489
+ const SEGMENT_BREAK_OPERATORS = new Set([
32490
+ "|",
32491
+ "|&",
32492
+ "||",
32493
+ "&&",
32494
+ ";",
32495
+ "&"
32496
+ ]);
32497
+ const GRAY_CONNECTORS = new Set([
32498
+ "|",
32499
+ "|&",
32500
+ "||",
32501
+ "&&"
32502
+ ]);
32503
+ function colorizeCommandLine(line, style) {
32504
+ if (!line) return line;
32505
+ const entries = parseShellEntries(line);
32506
+ if (!entries || entries.length === 0) return line;
32507
+ const rendered = [];
32508
+ let shouldHighlightExec = true;
32509
+ for (const entry of entries) {
32510
+ if (typeof entry === "string") {
32511
+ const token = entry;
32512
+ const isExecToken = shouldHighlightExec && !isEnvAssignmentToken(token) && !isExecPrefixToken(token);
32513
+ let styledToken = token;
32514
+ if (isExecToken) {
32515
+ styledToken = colorizeExecToken(token, style);
32516
+ shouldHighlightExec = false;
32517
+ } else styledToken = colorizeCommandSubstitutions(token, style);
32518
+ rendered.push(renderShellToken(token, styledToken));
32519
+ continue;
32520
+ }
32521
+ if (isShellCommentEntry(entry)) {
32522
+ rendered.push(`#${entry.comment}`);
32523
+ break;
32524
+ }
32525
+ if (!isShellOperatorEntry(entry)) continue;
32526
+ rendered.push(GRAY_CONNECTORS.has(entry.op) ? gray(entry.op, style) : entry.op);
32527
+ if (SEGMENT_BREAK_OPERATORS.has(entry.op)) shouldHighlightExec = true;
32528
+ }
32529
+ return rendered.join(" ");
32530
+ }
32531
+ function colorizeCommandProgram(display, style) {
32532
+ return display.replaceAll("\r\n", "\n").split("\n").map((line) => colorizeCommandLine(line, style)).join("\n");
32504
32533
  }
32505
32534
  function formatToolLabel(tool, style) {
32506
32535
  const name = tool.toolName ?? "<tool>";
32507
- if (name === "command_execution") {
32536
+ if (isWriteTool(tool.toolName)) return formatWriteToolLabel(tool, style);
32537
+ if (isEditTool(tool.toolName)) return formatEditToolLabel(tool, style);
32538
+ if (isCommandLikeTool(tool.toolName)) {
32508
32539
  const rawCommand = getCommandExecutionRawCommand(tool);
32509
32540
  const exploredLines = rawCommand ? getExploredCommandLines(rawCommand) : void 0;
32510
32541
  if (exploredLines && exploredLines.length > 1) return cyan(exploredLines[0] ?? "Explored", style);
32511
- const display = getCommandExecutionDisplayCommand(tool) ?? "(unknown command)";
32512
- return `${bold("$", style)} ${colorizeCommandProgram(display, style)}`;
32542
+ return colorizeCommandProgram(getCommandExecutionDisplayCommand(tool) ?? "(unknown command)", style);
32513
32543
  }
32514
- const renderedName = bold(name, style);
32544
+ const renderedName = bold$1(name, style);
32515
32545
  if (!tool.title) return renderedName;
32516
32546
  return `${renderedName}: ${tool.title}`;
32517
32547
  }
@@ -32542,12 +32572,37 @@ function formatToolStatusIcon(status, style) {
32542
32572
  }
32543
32573
  }
32544
32574
  function getExploredSubCommandLines(tool) {
32545
- if (tool.toolName !== "command_execution") return void 0;
32546
- const rawCommand = getCommandExecutionRawCommand(tool);
32547
- if (!rawCommand) return void 0;
32548
- const exploredLines = getExploredCommandLines(rawCommand);
32549
- if (!exploredLines || exploredLines.length <= 1) return void 0;
32550
- return exploredLines.slice(1);
32575
+ if (isCommandLikeTool(tool.toolName)) {
32576
+ const rawCommand = getCommandExecutionRawCommand(tool);
32577
+ if (!rawCommand) return void 0;
32578
+ const exploredLines = getExploredCommandLines(rawCommand);
32579
+ if (!exploredLines || exploredLines.length <= 1) return void 0;
32580
+ return exploredLines.slice(1);
32581
+ }
32582
+ if (!isClaudeExplorationTool(tool.toolName)) return void 0;
32583
+ const input = toRecord(tool.input) ?? {};
32584
+ const toolName = (tool.toolName ?? "").toLowerCase();
32585
+ if (toolName === "read") {
32586
+ const path = toStringOrUndefined(input.file_path) ?? toStringOrUndefined(input.path) ?? toStringOrUndefined(input.filePath);
32587
+ if (!path) return ["Read"];
32588
+ return [`Read ${path}`];
32589
+ }
32590
+ if (toolName === "glob") {
32591
+ const pattern = toStringOrUndefined(input.pattern);
32592
+ const path = toStringOrUndefined(input.path);
32593
+ if (path && pattern) return [`List ${path} (glob: ${pattern})`];
32594
+ if (path) return [`List ${path}`];
32595
+ if (pattern) return [`List (glob: ${pattern})`];
32596
+ return ["List"];
32597
+ }
32598
+ if (toolName === "grep") {
32599
+ const pattern = toStringOrUndefined(input.pattern);
32600
+ const path = toStringOrUndefined(input.path);
32601
+ if (pattern && path) return [`Search ${pattern} in ${path}`];
32602
+ if (pattern) return [`Search ${pattern}`];
32603
+ if (path) return [`Search in ${path}`];
32604
+ return ["Search"];
32605
+ }
32551
32606
  }
32552
32607
  function formatExploredSubCommandLine(line, style) {
32553
32608
  if (line.startsWith("Search ")) {
@@ -32556,7 +32611,7 @@ function formatExploredSubCommandLine(line, style) {
32556
32611
  if (at !== -1) {
32557
32612
  const terms = payload.slice(0, at);
32558
32613
  const file = payload.slice(at + 4);
32559
- return `${cyan("Search", style)} ${terms} ${dimGray("in", style)} ${file}`;
32614
+ return `${cyan("Search", style)} ${terms} ${dimGray$1("in", style)} ${file}`;
32560
32615
  }
32561
32616
  return `${cyan("Search", style)} ${payload}`;
32562
32617
  }
@@ -32568,7 +32623,7 @@ function formatExploredSubCommandLine(line, style) {
32568
32623
  function toDisplayLines(text) {
32569
32624
  return text.replaceAll("\r\n", "\n").split("\n");
32570
32625
  }
32571
- function wrapRawLineToWidth(line, width) {
32626
+ function wrapRawLineToWidth$1(line, width) {
32572
32627
  if (width <= 0) return [line];
32573
32628
  if (line.length === 0) return [""];
32574
32629
  const out = [];
@@ -32595,21 +32650,33 @@ function trimCommandOutputTopBottom(lines) {
32595
32650
  lines[lines.length - 1]
32596
32651
  ];
32597
32652
  }
32653
+ function getCommandLikeOutputText(output) {
32654
+ const value = output;
32655
+ const aggregated = typeof value?.aggregated_output === "string" ? value.aggregated_output : "";
32656
+ if (aggregated) return aggregated;
32657
+ const nestedToolResult = value?.tool_use_result;
32658
+ const stdout = typeof nestedToolResult?.stdout === "string" ? nestedToolResult.stdout : typeof value?.stdout === "string" ? value.stdout : "";
32659
+ const stderr = typeof nestedToolResult?.stderr === "string" ? nestedToolResult.stderr : typeof value?.stderr === "string" ? value.stderr : "";
32660
+ if (stdout || stderr) {
32661
+ if (stdout && stderr) return `${stdout}${stdout.endsWith("\n") ? "" : "\n"}${stderr}`;
32662
+ return stdout || stderr;
32663
+ }
32664
+ if (typeof value?.content === "string") return value.content;
32665
+ }
32598
32666
  function formatToolResultPreviewLines(tool, options) {
32599
32667
  if (tool.status === "in_progress") return [];
32600
32668
  if (options.maxLines <= 0) return [];
32601
- if (tool.toolName === "command_execution") {
32669
+ if (isCommandLikeTool(tool.toolName)) {
32602
32670
  const rawCommand = getCommandExecutionRawCommand(tool);
32603
32671
  if (rawCommand && isExploredCommandExecution(rawCommand)) return [];
32604
32672
  }
32605
32673
  if (tool.status === "failed") {
32606
32674
  const lines = toDisplayLines(tool.errorText ?? "(no error text)");
32607
- return trimLinesWithEllipsis(tool.toolName === "command_execution" ? trimCommandOutputTopBottom(lines) : lines, options.maxLines);
32675
+ return trimLinesWithEllipsis(isCommandLikeTool(tool.toolName) ? trimCommandOutputTopBottom(lines) : lines, options.maxLines);
32608
32676
  }
32609
32677
  if (tool.output == null) return ["(none)"];
32610
- if (tool.toolName === "command_execution") {
32611
- const output = tool.output;
32612
- const allLines = toDisplayLines((typeof output?.aggregated_output === "string" ? output.aggregated_output : "").trimEnd());
32678
+ if (isCommandLikeTool(tool.toolName)) {
32679
+ const allLines = toDisplayLines((getCommandLikeOutputText(tool.output) ?? "").trimEnd());
32613
32680
  if (!allLines.some((line) => line.length > 0)) return ["(none)"];
32614
32681
  return trimLinesWithEllipsis(trimCommandOutputTopBottom(allLines), options.maxLines);
32615
32682
  }
@@ -32621,7 +32688,124 @@ function formatToolResultPreviewLines(tool, options) {
32621
32688
  breakLength: Math.max(20, options.widthHint)
32622
32689
  })), options.maxLines);
32623
32690
  }
32691
+ function formatWatchLogLines(options) {
32692
+ const blocks = [];
32693
+ const reasoningWrapWidth = Math.max(1, options.widthHint);
32694
+ const messageWrapWidth = Math.max(1, options.widthHint);
32695
+ const toolById = new Map(options.snapshot.toolActions.map((tool) => [tool.toolCallId, tool]));
32696
+ function pushBlock(order, lines) {
32697
+ if (lines.length === 0) return;
32698
+ blocks.push({
32699
+ order: order ?? Number.MAX_SAFE_INTEGER,
32700
+ lines
32701
+ });
32702
+ }
32703
+ for (let timelineIndex = 0; timelineIndex < options.snapshot.timeline.length; timelineIndex++) {
32704
+ const entry = options.snapshot.timeline[timelineIndex];
32705
+ if (entry.kind === "reasoning") {
32706
+ const reasoningBlock = [];
32707
+ const hasTrailingLineFeed = entry.text.endsWith("\n");
32708
+ const rawReasoningLines = toDisplayLines(entry.text);
32709
+ for (const rawLine of rawReasoningLines) wrapRawLineToWidth$1(rawLine, reasoningWrapWidth).forEach((chunk) => reasoningBlock.push(gray(chunk, options.style)));
32710
+ if (!hasTrailingLineFeed) reasoningBlock.push("");
32711
+ pushBlock(entry.order, reasoningBlock);
32712
+ continue;
32713
+ }
32714
+ if (entry.kind === "message") {
32715
+ const messageBlock = [];
32716
+ const rawMessageLines = toDisplayLines(entry.text);
32717
+ for (const rawLine of rawMessageLines) wrapRawLineToWidth$1(rawLine, messageWrapWidth).forEach((chunk) => messageBlock.push(chunk));
32718
+ pushBlock(entry.order, messageBlock);
32719
+ continue;
32720
+ }
32721
+ if (entry.kind === "warning") {
32722
+ pushBlock(entry.order, [kv("warning", yellow(entry.message, options.style), options.style)]);
32723
+ continue;
32724
+ }
32725
+ const tool = toolById.get(entry.toolCallId);
32726
+ if (!tool) continue;
32727
+ const exploredSubLines = getExploredSubCommandLines(tool);
32728
+ if (exploredSubLines != null) {
32729
+ const groupedTools = [tool];
32730
+ const groupedSubLines = [...exploredSubLines];
32731
+ let lookahead = timelineIndex + 1;
32732
+ while (lookahead < options.snapshot.timeline.length) {
32733
+ const next = options.snapshot.timeline[lookahead];
32734
+ if (next.kind !== "tool") break;
32735
+ const nextTool = toolById.get(next.toolCallId);
32736
+ if (!nextTool) break;
32737
+ const nextSubLines = getExploredSubCommandLines(nextTool);
32738
+ if (nextSubLines == null) break;
32739
+ groupedTools.push(nextTool);
32740
+ groupedSubLines.push(...nextSubLines);
32741
+ lookahead += 1;
32742
+ }
32743
+ const groupedBlock = [` ${[
32744
+ formatToolStatusIcon(combineToolStatuses(groupedTools.map((groupedTool) => groupedTool.status)), options.style),
32745
+ cyan("Explored", options.style),
32746
+ groupedTools.length > 1 ? gray(`x${groupedTools.length}`, options.style) : void 0
32747
+ ].filter(Boolean).join(" ")}`];
32748
+ groupedSubLines.forEach((subLine, subIndex) => groupedBlock.push(subIndex === 0 ? ` ${dimGray$1("└", options.style)} ${formatExploredSubCommandLine(subLine, options.style)}` : ` ${formatExploredSubCommandLine(subLine, options.style)}`));
32749
+ pushBlock(entry.order, groupedBlock);
32750
+ timelineIndex = lookahead - 1;
32751
+ continue;
32752
+ }
32753
+ const toolBlock = [];
32754
+ const summaryLines = toDisplayLines(formatToolSummaryLine(tool, options.style));
32755
+ const summaryContinuationIndent = isCommandLikeTool(tool.toolName) ? " " : " ";
32756
+ summaryLines.forEach((line, index) => toolBlock.push(index === 0 ? ` ${line}` : `${summaryContinuationIndent}${line}`));
32757
+ if (isCommandLikeTool(tool.toolName)) formatToolResultPreviewLines(tool, {
32758
+ maxLines: Number.POSITIVE_INFINITY,
32759
+ widthHint: Math.max(20, options.widthHint - 10)
32760
+ }).forEach((line, index) => toolBlock.push(index === 0 ? ` ${dimGray$1("└", options.style)} ${dimGray$1(line, options.style)}` : ` ${dimGray$1(line, options.style)}`));
32761
+ pushBlock(entry.order, toolBlock);
32762
+ }
32763
+ if (options.lastError) pushBlock(void 0, [kv("error", red(options.lastError, options.style), options.style)]);
32764
+ blocks.sort((a, b) => a.order - b.order);
32765
+ const lines = [];
32766
+ for (const block of blocks) {
32767
+ if (lines.length > 0 && lines[lines.length - 1] !== "") lines.push("");
32768
+ lines.push(...block.lines);
32769
+ }
32770
+ if (lines.length === 0) lines.push(gray("(waiting for formatted stream output)", options.style));
32771
+ return lines;
32772
+ }
32773
+
32774
+ //#endregion
32775
+ //#region src/cli/commands/watch/source.ts
32776
+ function toStreamSourceFromExecuteAgent(executeAgent) {
32777
+ if (!executeAgent) return void 0;
32778
+ const normalized = executeAgent.toLowerCase();
32779
+ if (normalized.includes("claude")) return "claude-code";
32780
+ if (normalized.includes("codex")) return "codex";
32781
+ }
32782
+ function detectStreamSourceFromRawPayload(payload) {
32783
+ if (payload.includes("\"type\":\"thread.") || payload.includes("\"type\":\"turn.") || payload.includes("\"type\":\"item.")) return "codex";
32784
+ if (payload.includes("\"type\":\"message_start\"") || payload.includes("\"type\":\"message_delta\"") || payload.includes("\"type\":\"message_stop\"") || payload.includes("\"type\":\"content_block_start\"") || payload.includes("\"type\":\"content_block_delta\"") || payload.includes("\"type\":\"content_block_stop\"")) return "claude-code";
32785
+ }
32786
+ function parseAgentStreamSourceOption(value) {
32787
+ const normalized = value.trim().toLowerCase();
32788
+ if (normalized === "codex") return "codex";
32789
+ if (normalized === "claude-code" || normalized === "claude") return "claude-code";
32790
+ throw new Error(`Invalid stream source: ${value}. Allowed: codex, claude-code`);
32791
+ }
32792
+
32793
+ //#endregion
32794
+ //#region src/cli/commands/watch/stream-url.ts
32795
+ const PANTHEON_BASE_URL = "https://pantheon-ai.tidb.ai";
32796
+ function makeOpaqueStreamUrl(streamId) {
32797
+ return `${PANTHEON_BASE_URL}/ai_stream_proxy/v2/streams/${encodeURIComponent(streamId)}/stream?format=opaque-stream-json`;
32798
+ }
32799
+
32800
+ //#endregion
32801
+ //#region src/cli/commands/watch/tui.ts
32802
+ const ANSI_RESET = "\x1B[0m";
32624
32803
  const ANSI_SGR_REGEX = /\u001b\[[0-9;]*m/g;
32804
+ function useAnsiStyles() {
32805
+ if (!process$1.stdout.isTTY) return false;
32806
+ if (process$1.env.NO_COLOR != null) return false;
32807
+ return true;
32808
+ }
32625
32809
  function visibleLength(value) {
32626
32810
  return value.replaceAll(ANSI_SGR_REGEX, "").length;
32627
32811
  }
@@ -32649,7 +32833,142 @@ function clip(value, maxVisible) {
32649
32833
  if (visibleLength(value) <= maxVisible) return value;
32650
32834
  if (maxVisible === 1) return "…";
32651
32835
  const sliced = sliceSgrByVisibleWidth(value, maxVisible - 1);
32652
- return `${sliced}…${sliced.includes("\x1B[") ? ANSI.reset : ""}`;
32836
+ return `${sliced}…${sliced.includes("\x1B[") ? ANSI_RESET : ""}`;
32837
+ }
32838
+
32839
+ //#endregion
32840
+ //#region src/cli/commands/watch.ts
32841
+ function parseListWidth(value) {
32842
+ const width = Number(value);
32843
+ if (!Number.isInteger(width) || width <= 0) throw new InvalidArgumentError("list-width requires a positive integer, e.g. 32");
32844
+ return width;
32845
+ }
32846
+ function ensureEnvOrExit(keys) {
32847
+ const missing = keys.filter((key) => !process.env[key]);
32848
+ if (missing.length === 0) return;
32849
+ for (const key of missing) console.error(`${key} environment variable is not set.`);
32850
+ process.exit(1);
32851
+ }
32852
+ async function getAgentExecuteAgentMap(db, targets) {
32853
+ const agents = Array.from(new Set(targets.map((t) => t.agent)));
32854
+ if (agents.length === 0) return /* @__PURE__ */ new Map();
32855
+ const configs = await db.selectFrom("agent_project_config").select([
32856
+ "agent",
32857
+ "project_id",
32858
+ "execute_agent"
32859
+ ]).where("agent", "in", agents).execute();
32860
+ const map = /* @__PURE__ */ new Map();
32861
+ for (const row of configs) map.set(`${row.agent}|${row.project_id}`, row.execute_agent);
32862
+ return map;
32863
+ }
32864
+ async function loadTasksByIds(db, ids) {
32865
+ if (ids.length === 0) return [];
32866
+ const rows = await db.selectFrom("task").selectAll().where("id", "in", ids).execute();
32867
+ const out = [];
32868
+ for (const row of rows) {
32869
+ const parsed = taskItemSchema.safeParse(row);
32870
+ if (!parsed.success) {
32871
+ console.error(`Failed to parse task row id=${row.id} agent=${row.agent} status=${row.status}: ${parsed.error.message}`);
32872
+ continue;
32873
+ }
32874
+ out.push({
32875
+ agent: row.agent,
32876
+ task: parsed.data
32877
+ });
32878
+ }
32879
+ return out;
32880
+ }
32881
+ async function loadTasksForAgent(db, agent) {
32882
+ const rows = await db.selectFrom("task").selectAll().where("agent", "=", agent).execute();
32883
+ const out = [];
32884
+ for (const row of rows) {
32885
+ const parsed = taskItemSchema.safeParse(row);
32886
+ if (!parsed.success) continue;
32887
+ out.push(parsed.data);
32888
+ }
32889
+ return out;
32890
+ }
32891
+ async function loadAllTasks(db, limit) {
32892
+ const rows = await db.selectFrom("task").selectAll().orderBy("queued_at", "desc").limit(limit).execute();
32893
+ const out = [];
32894
+ for (const row of rows) {
32895
+ const parsed = taskItemSchema.safeParse(row);
32896
+ if (!parsed.success) continue;
32897
+ out.push({
32898
+ agent: row.agent,
32899
+ task: parsed.data
32900
+ });
32901
+ }
32902
+ return out;
32903
+ }
32904
+ async function resolveWatchTargets(options) {
32905
+ const warnings = [];
32906
+ let selected = [];
32907
+ if (options.taskIds.length > 0) {
32908
+ const uniqueTaskIds = Array.from(new Set(options.taskIds));
32909
+ const found = await loadTasksByIds(options.db, uniqueTaskIds);
32910
+ const foundIds = new Set(found.map((t) => t.task.id));
32911
+ const missing = uniqueTaskIds.filter((id) => !foundIds.has(id));
32912
+ if (missing.length > 0) warnings.push(`Requested task IDs not found: ${missing.join(", ")}`);
32913
+ const byId = new Map(found.map((t) => [t.task.id, t]));
32914
+ selected = uniqueTaskIds.map((id) => byId.get(id)).filter(Boolean);
32915
+ if (selected.length === 0) warnings.push("No requested tasks found; falling back to default selection.");
32916
+ } else if (options.agentNames.length > 0) {
32917
+ const uniqueAgents = Array.from(new Set(options.agentNames));
32918
+ if (uniqueAgents.length === 1) {
32919
+ selected = selectSingleAgentWatchTasks(await loadTasksForAgent(options.db, uniqueAgents[0]), { finishedLimit: 3 }).map((task) => ({
32920
+ agent: uniqueAgents[0],
32921
+ task
32922
+ }));
32923
+ if (selected.length === 0) warnings.push(`No tasks found for agent ${uniqueAgents[0]}; falling back to default selection.`);
32924
+ } else {
32925
+ selected = selectMultiAgentWatchTasks(await Promise.all(uniqueAgents.map(async (agent) => {
32926
+ return (await loadTasksForAgent(options.db, agent)).map((task) => ({
32927
+ agent,
32928
+ task
32929
+ }));
32930
+ })).then((groups) => groups.flat()), uniqueAgents);
32931
+ const foundAgents = new Set(selected.map((t) => t.agent));
32932
+ const missing = uniqueAgents.filter((agent) => !foundAgents.has(agent));
32933
+ if (missing.length > 0) warnings.push(`No tasks found for agent(s): ${missing.join(", ")}`);
32934
+ if (selected.length === 0) warnings.push("No requested agent tasks found; falling back to default selection.");
32935
+ }
32936
+ } else warnings.push("No targets specified; selecting up to 3 agents automatically.");
32937
+ if (selected.length === 0) selected = selectFallbackWatchTasks(await loadAllTasks(options.db, 500), { agentLimit: 3 });
32938
+ const executeAgentMap = await getAgentExecuteAgentMap(options.db, selected);
32939
+ return {
32940
+ targets: selected.map((entry) => ({
32941
+ ...entry,
32942
+ executeAgent: executeAgentMap.get(`${entry.agent}|${entry.task.project_id}`)
32943
+ })),
32944
+ warnings
32945
+ };
32946
+ }
32947
+ const ANSI = {
32948
+ reset: "\x1B[0m",
32949
+ bold: "\x1B[1m",
32950
+ dim: "\x1B[2m",
32951
+ red: "\x1B[31m",
32952
+ green: "\x1B[32m",
32953
+ yellow: "\x1B[33m",
32954
+ cyan: "\x1B[36m",
32955
+ gray: "\x1B[90m"
32956
+ };
32957
+ function sgrWrap(text, code, enabled) {
32958
+ return enabled ? `${code}${text}${ANSI.reset}` : text;
32959
+ }
32960
+ function bold(text, enabled) {
32961
+ return sgrWrap(text, ANSI.bold, enabled);
32962
+ }
32963
+ function dimGray(text, enabled) {
32964
+ return sgrWrap(text, `${ANSI.dim}${ANSI.gray}`, enabled);
32965
+ }
32966
+ function wrapRawLineToWidth(line, width) {
32967
+ if (width <= 0) return [line];
32968
+ if (line.length === 0) return [""];
32969
+ const out = [];
32970
+ for (let i = 0; i < line.length; i += width) out.push(line.slice(i, i + width));
32971
+ return out;
32653
32972
  }
32654
32973
  const TODO_PREVIEW_MAX_ITEMS = 3;
32655
32974
  const TODO_PREVIEW_MAX_PENDING = 2;
@@ -32751,7 +33070,7 @@ function createWatchCommand(version) {
32751
33070
  rt.streamDone = false;
32752
33071
  const preferred = toStreamSourceFromExecuteAgent(rt.target.executeAgent);
32753
33072
  rt.source = preferred;
32754
- const url = makeStreamUrl(streamId);
33073
+ const url = makeOpaqueStreamUrl(streamId);
32755
33074
  (async () => {
32756
33075
  try {
32757
33076
  const res = await fetch(url, {
@@ -32914,86 +33233,12 @@ function createWatchCommand(version) {
32914
33233
  return runtime[selectedIndex];
32915
33234
  }
32916
33235
  function getFormattedLogLines(rt, options) {
32917
- const step = rt.aggregator.snapshot();
32918
- const blocks = [];
32919
- const reasoningWrapWidth = Math.max(1, options.widthHint);
32920
- const messageWrapWidth = Math.max(1, options.widthHint);
32921
- const toolById = new Map(step.toolActions.map((tool) => [tool.toolCallId, tool]));
32922
- function pushBlock(order, lines) {
32923
- if (lines.length === 0) return;
32924
- blocks.push({
32925
- order: order ?? Number.MAX_SAFE_INTEGER,
32926
- lines
32927
- });
32928
- }
32929
- for (let timelineIndex = 0; timelineIndex < step.timeline.length; timelineIndex++) {
32930
- const entry = step.timeline[timelineIndex];
32931
- if (entry.kind === "reasoning") {
32932
- const reasoningBlock = [];
32933
- const hasTrailingLineFeed = entry.text.endsWith("\n");
32934
- const rawReasoningLines = toDisplayLines(entry.text);
32935
- for (const rawLine of rawReasoningLines) wrapRawLineToWidth(rawLine, reasoningWrapWidth).forEach((chunk) => reasoningBlock.push(gray(chunk, options.style)));
32936
- if (!hasTrailingLineFeed) reasoningBlock.push("");
32937
- pushBlock(entry.order, reasoningBlock);
32938
- continue;
32939
- }
32940
- if (entry.kind === "message") {
32941
- const messageBlock = [];
32942
- const rawMessageLines = toDisplayLines(entry.text);
32943
- for (const rawLine of rawMessageLines) wrapRawLineToWidth(rawLine, messageWrapWidth).forEach((chunk) => messageBlock.push(chunk));
32944
- pushBlock(entry.order, messageBlock);
32945
- continue;
32946
- }
32947
- if (entry.kind === "warning") {
32948
- pushBlock(entry.order, [kv("warning", yellow(entry.message, options.style), options.style)]);
32949
- continue;
32950
- }
32951
- const tool = toolById.get(entry.toolCallId);
32952
- if (!tool) continue;
32953
- const exploredSubLines = getExploredSubCommandLines(tool);
32954
- if (exploredSubLines != null) {
32955
- const groupedTools = [tool];
32956
- const groupedSubLines = [...exploredSubLines];
32957
- let lookahead = timelineIndex + 1;
32958
- while (lookahead < step.timeline.length) {
32959
- const next = step.timeline[lookahead];
32960
- if (next.kind !== "tool") break;
32961
- const nextTool = toolById.get(next.toolCallId);
32962
- if (!nextTool) break;
32963
- const nextSubLines = getExploredSubCommandLines(nextTool);
32964
- if (nextSubLines == null) break;
32965
- groupedTools.push(nextTool);
32966
- groupedSubLines.push(...nextSubLines);
32967
- lookahead += 1;
32968
- }
32969
- const groupedBlock = [` ${[
32970
- formatToolStatusIcon(combineToolStatuses(groupedTools.map((groupedTool) => groupedTool.status)), options.style),
32971
- cyan("Explored", options.style),
32972
- groupedTools.length > 1 ? gray(`x${groupedTools.length}`, options.style) : void 0
32973
- ].filter(Boolean).join(" ")}`];
32974
- groupedSubLines.forEach((subLine, subIndex) => groupedBlock.push(subIndex === 0 ? ` ${dimGray("└", options.style)} ${formatExploredSubCommandLine(subLine, options.style)}` : ` ${formatExploredSubCommandLine(subLine, options.style)}`));
32975
- pushBlock(entry.order, groupedBlock);
32976
- timelineIndex = lookahead - 1;
32977
- continue;
32978
- }
32979
- const toolBlock = [];
32980
- toDisplayLines(formatToolSummaryLine(tool, options.style)).forEach((line, index) => toolBlock.push(index === 0 ? ` ${line}` : ` ${line}`));
32981
- if (tool.toolName === "command_execution") formatToolResultPreviewLines(tool, {
32982
- maxLines: Number.MAX_SAFE_INTEGER,
32983
- widthHint: Math.max(20, options.widthHint - 10),
32984
- style: options.style
32985
- }).forEach((line, index) => toolBlock.push(index === 0 ? ` ${dimGray("└", options.style)} ${dimGray(line, options.style)}` : ` ${dimGray(line, options.style)}`));
32986
- pushBlock(entry.order, toolBlock);
32987
- }
32988
- if (rt.lastError) pushBlock(void 0, [kv("error", red(rt.lastError, options.style), options.style)]);
32989
- blocks.sort((a, b) => a.order - b.order);
32990
- const lines = [];
32991
- for (const block of blocks) {
32992
- if (lines.length > 0 && lines[lines.length - 1] !== "") lines.push("");
32993
- lines.push(...block.lines);
32994
- }
32995
- if (lines.length === 0) lines.push(gray("(waiting for formatted stream output)", options.style));
32996
- return lines;
33236
+ return formatWatchLogLines({
33237
+ snapshot: rt.aggregator.snapshot(),
33238
+ lastError: rt.lastError,
33239
+ style: options.style,
33240
+ widthHint: options.widthHint
33241
+ });
32997
33242
  }
32998
33243
  function moveTaskSelection(delta) {
32999
33244
  if (runtime.length === 0) return;
@@ -33189,9 +33434,251 @@ function createWatchCommand(version) {
33189
33434
  });
33190
33435
  }
33191
33436
 
33437
+ //#endregion
33438
+ //#region src/cli/commands/watch-stream.ts
33439
+ function parseSourceOption(value) {
33440
+ try {
33441
+ return parseAgentStreamSourceOption(value);
33442
+ } catch (error) {
33443
+ throw new InvalidArgumentError(error instanceof Error ? error.message : String(error));
33444
+ }
33445
+ }
33446
+ function createWatchStreamCommand(version) {
33447
+ return createCommand("watch-stream").version(version).description("Watch a single stream and show formatted live logs.").argument("<stream-id>", "The stream id to watch.").option("--source <source>", "Force stream source (codex or claude-code).", parseSourceOption).action(async function() {
33448
+ if (!ensureEnv(["PANTHEON_API_KEY"])) return;
33449
+ const [streamId] = this.args;
33450
+ const options = this.opts();
33451
+ if (!process$1.stdout.isTTY) {
33452
+ console.error("watch-stream requires a TTY.");
33453
+ process$1.exit(1);
33454
+ }
33455
+ const aggregator = new WatchStepAggregator({ maxLogLines: Number.POSITIVE_INFINITY });
33456
+ let source = options.source;
33457
+ let streamEnded = false;
33458
+ let abortedByIdleTimeout = false;
33459
+ let lastError;
33460
+ const abortController = new AbortController();
33461
+ const onSigInt = () => abortController.abort();
33462
+ process$1.on("SIGINT", onSigInt);
33463
+ const screen = blessed.screen({
33464
+ smartCSR: true,
33465
+ title: "pantheon-agents watch-stream"
33466
+ });
33467
+ screen.key(["q", "C-c"], () => abortController.abort());
33468
+ const pane = blessed.box({
33469
+ parent: screen,
33470
+ top: 0,
33471
+ left: 0,
33472
+ height: "100%-1",
33473
+ width: "100%",
33474
+ border: "line",
33475
+ tags: false,
33476
+ wrap: false,
33477
+ style: { border: { fg: "cyan" } }
33478
+ });
33479
+ const header = blessed.box({
33480
+ parent: pane,
33481
+ top: 0,
33482
+ left: 1,
33483
+ height: 2,
33484
+ width: "100%-2",
33485
+ tags: false,
33486
+ wrap: false
33487
+ });
33488
+ const body = blessed.box({
33489
+ parent: pane,
33490
+ top: 2,
33491
+ left: 1,
33492
+ width: "100%-2",
33493
+ height: 1,
33494
+ tags: false,
33495
+ wrap: false,
33496
+ scrollable: false
33497
+ });
33498
+ const footer = blessed.box({
33499
+ parent: screen,
33500
+ bottom: 0,
33501
+ left: 0,
33502
+ height: 1,
33503
+ width: "100%",
33504
+ tags: false,
33505
+ wrap: false,
33506
+ style: { fg: "gray" }
33507
+ });
33508
+ const viewport = {
33509
+ autoFollow: true,
33510
+ scrollOffset: 0
33511
+ };
33512
+ function getFormattedLogLines(style, widthHint) {
33513
+ return formatWatchLogLines({
33514
+ snapshot: aggregator.snapshot(),
33515
+ lastError,
33516
+ style,
33517
+ widthHint
33518
+ });
33519
+ }
33520
+ function scrollLog(delta) {
33521
+ const bodyHeight = typeof body.height === "number" ? body.height : 0;
33522
+ if (bodyHeight <= 0) return;
33523
+ const bodyWidth = typeof body.width === "number" ? body.width : 80;
33524
+ const lines = getFormattedLogLines(useAnsiStyles(), Math.max(20, bodyWidth));
33525
+ const maxOffset = Math.max(0, lines.length - bodyHeight);
33526
+ viewport.autoFollow = false;
33527
+ viewport.scrollOffset = Math.max(0, Math.min(maxOffset, viewport.scrollOffset + delta));
33528
+ }
33529
+ function scrollToTop() {
33530
+ viewport.autoFollow = false;
33531
+ viewport.scrollOffset = 0;
33532
+ }
33533
+ function scrollToBottom() {
33534
+ const bodyHeight = typeof body.height === "number" ? body.height : 0;
33535
+ if (bodyHeight <= 0) return;
33536
+ const bodyWidth = typeof body.width === "number" ? body.width : 80;
33537
+ const lines = getFormattedLogLines(useAnsiStyles(), Math.max(20, bodyWidth));
33538
+ const maxOffset = Math.max(0, lines.length - bodyHeight);
33539
+ viewport.autoFollow = false;
33540
+ viewport.scrollOffset = maxOffset;
33541
+ }
33542
+ function getHalfScreenScrollStep() {
33543
+ const bodyHeight = typeof body.height === "number" ? body.height : 0;
33544
+ return Math.max(1, Math.floor(bodyHeight / 2));
33545
+ }
33546
+ screen.key(["up"], () => {
33547
+ scrollLog(-getHalfScreenScrollStep());
33548
+ });
33549
+ screen.key(["down"], () => {
33550
+ scrollLog(getHalfScreenScrollStep());
33551
+ });
33552
+ screen.key(["g", "home"], () => {
33553
+ scrollToTop();
33554
+ });
33555
+ screen.key([
33556
+ "G",
33557
+ "S-g",
33558
+ "end"
33559
+ ], () => {
33560
+ scrollToBottom();
33561
+ });
33562
+ screen.key(["f"], () => {
33563
+ viewport.autoFollow = true;
33564
+ });
33565
+ const renderTimer = setInterval(() => {
33566
+ const style = useAnsiStyles();
33567
+ const columns = Math.max(40, screen.width ?? 120);
33568
+ const rows = Math.max(8, screen.height ?? 24);
33569
+ const contentHeight = Math.max(1, rows - 1);
33570
+ pane.top = 0;
33571
+ pane.left = 0;
33572
+ pane.width = columns;
33573
+ pane.height = contentHeight;
33574
+ pane.setLabel(" Stream Logs ");
33575
+ const paneInnerWidth = Math.max(1, columns - 2);
33576
+ const paneInnerHeight = Math.max(1, contentHeight - 2);
33577
+ const headerHeight = Math.min(2, paneInnerHeight);
33578
+ const bodyHeight = Math.max(0, paneInnerHeight - headerHeight);
33579
+ header.top = 0;
33580
+ header.left = 1;
33581
+ header.width = paneInnerWidth;
33582
+ header.height = headerHeight;
33583
+ const headerLines = [clip(`stream id: ${streamId}`, paneInnerWidth), clip(`source: ${source ?? "(detecting...)"} • status: ${streamEnded ? "ended" : "streaming"}${abortedByIdleTimeout ? " (idle timeout)" : ""}`, paneInnerWidth)];
33584
+ header.setContent(headerLines.slice(0, headerHeight).join("\n"));
33585
+ body.top = headerHeight;
33586
+ body.left = 1;
33587
+ body.width = paneInnerWidth;
33588
+ body.height = bodyHeight;
33589
+ const lines = getFormattedLogLines(style, paneInnerWidth);
33590
+ const maxOffset = Math.max(0, lines.length - bodyHeight);
33591
+ if (viewport.autoFollow) viewport.scrollOffset = maxOffset;
33592
+ else viewport.scrollOffset = Math.max(0, Math.min(maxOffset, viewport.scrollOffset));
33593
+ if (bodyHeight <= 0) body.hide();
33594
+ else {
33595
+ const visibleLines = lines.slice(viewport.scrollOffset, viewport.scrollOffset + bodyHeight);
33596
+ body.setContent(visibleLines.map((line) => clip(line, paneInnerWidth)).join("\n"));
33597
+ body.show();
33598
+ }
33599
+ const footerParts = [
33600
+ "Ctrl+C to exit",
33601
+ "q to quit",
33602
+ "↑/↓ half-page",
33603
+ "g/G top/bottom",
33604
+ "f resume auto-scroll"
33605
+ ];
33606
+ if (!viewport.autoFollow) footerParts.push("auto-scroll paused");
33607
+ if (lastError) footerParts.push(`error: ${clip(lastError, 40)}`);
33608
+ footer.setContent(footerParts.join(" • "));
33609
+ screen.render();
33610
+ }, 300);
33611
+ const streamAbort = new AbortController();
33612
+ (async () => {
33613
+ try {
33614
+ const res = await fetch(makeOpaqueStreamUrl(streamId), {
33615
+ headers: {
33616
+ Authorization: `Bearer ${process$1.env.PANTHEON_API_KEY}`,
33617
+ Accept: "text/event-stream"
33618
+ },
33619
+ signal: streamAbort.signal
33620
+ });
33621
+ if (!res.ok) {
33622
+ lastError = `stream request failed: ${res.status} ${res.statusText}`;
33623
+ streamEnded = true;
33624
+ return;
33625
+ }
33626
+ if (!res.body) {
33627
+ lastError = "missing response body";
33628
+ streamEnded = true;
33629
+ return;
33630
+ }
33631
+ let normalizer;
33632
+ const consumeResult = await consumeOpaqueSseStream({
33633
+ stream: res.body,
33634
+ signal: streamAbort.signal,
33635
+ onWarning: (warning) => aggregator.pushUiChunk({
33636
+ type: "data-agent-unknown",
33637
+ data: { reason: warning }
33638
+ }),
33639
+ onData: (payload) => {
33640
+ if (!normalizer) {
33641
+ source = source ?? detectStreamSourceFromRawPayload(payload) ?? "codex";
33642
+ normalizer = createAgentStreamNormalizer({
33643
+ source,
33644
+ emitStartChunk: false,
33645
+ includeMetaEvents: true,
33646
+ unknownChunkPolicy: "emit"
33647
+ });
33648
+ }
33649
+ aggregator.pushUiChunks(normalizer.push(payload));
33650
+ }
33651
+ });
33652
+ if (normalizer) aggregator.pushUiChunks(normalizer.finish());
33653
+ streamEnded = consumeResult.ended;
33654
+ abortedByIdleTimeout = consumeResult.abortedByIdleTimeout;
33655
+ } catch (error) {
33656
+ if (streamAbort.signal.aborted) {
33657
+ streamEnded = true;
33658
+ return;
33659
+ }
33660
+ lastError = error instanceof Error ? error.message : String(error);
33661
+ streamEnded = true;
33662
+ }
33663
+ })();
33664
+ try {
33665
+ await new Promise((resolve) => {
33666
+ const resolveOnce = () => resolve();
33667
+ abortController.signal.addEventListener("abort", resolveOnce, { once: true });
33668
+ if (abortController.signal.aborted) resolveOnce();
33669
+ });
33670
+ } finally {
33671
+ process$1.off("SIGINT", onSigInt);
33672
+ clearInterval(renderTimer);
33673
+ streamAbort.abort();
33674
+ screen.destroy();
33675
+ }
33676
+ });
33677
+ }
33678
+
33192
33679
  //#endregion
33193
33680
  //#region src/cli/index.ts
33194
- const program = new Command().name("pantheon-agents").description("Pantheon agents CLI").version(version$1).showHelpAfterError().showSuggestionAfterError().addHelpCommand().addCommand(createAddTaskCommand(version$1)).addCommand(createConfigAgentCommand(version$1)).addCommand(createDeleteTaskCommand(version$1)).addCommand(createCancelTaskCommand(version$1)).addCommand(createReconfigAgentCommand(version$1)).addCommand(createRunAgentCommand(version$1)).addCommand(createServerCommand(version$1)).addCommand(createShowConfigCommand(version$1)).addCommand(createShowTasksCommand(version$1)).addCommand(createWatchCommand(version$1)).addCommand(createLlmExplainCommand(version$1));
33681
+ const program = new Command().name("pantheon-agents").description("Pantheon agents CLI").version(version$1).showHelpAfterError().showSuggestionAfterError().addHelpCommand().addCommand(createAddTaskCommand(version$1)).addCommand(createConfigAgentCommand(version$1)).addCommand(createDeleteTaskCommand(version$1)).addCommand(createCancelTaskCommand(version$1)).addCommand(createReconfigAgentCommand(version$1)).addCommand(createRunAgentCommand(version$1)).addCommand(createServerCommand(version$1)).addCommand(createShowConfigCommand(version$1)).addCommand(createShowTasksCommand(version$1)).addCommand(createWatchCommand(version$1)).addCommand(createWatchStreamCommand(version$1)).addCommand(createLlmExplainCommand(version$1));
33195
33682
  async function main() {
33196
33683
  if (process$1.argv.length <= 2) {
33197
33684
  program.outputHelp();