@pantheon.ai/agents 0.0.10 → 0.0.11

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 +43 -6
  2. package/dist/index.js +384 -151
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -1,16 +1,43 @@
1
1
  # `@pantheon.ai/agents`
2
2
 
3
- This package contains:
3
+ This package provides the `pantheon-agents` CLI for managing agent configs and tasks backed by a TiDB/MySQL task list.
4
+
5
+ It includes:
4
6
 
5
7
  - A **runner loop** (`pantheon-agents run <agent>`) that executes queued tasks via Pantheon and persists status/output to TiDB.
6
8
  - A **client-facing server** (`pantheon-agents server`) that exposes the same task operations over **HTTP REST** and **MCP (HTTP+SSE)**.
7
9
 
8
- ## Prerequisites
10
+ ## Build
11
+
12
+ ```bash
13
+ npm run -w @pantheon.ai/agents build
14
+ ```
15
+
16
+ ## Environment variables
17
+
18
+ - `DATABASE_URL` (required): MySQL/TiDB connection string for the task list database.
19
+ - `PANTHEON_API_KEY` (required for some commands): Used to query/execute Pantheon branches.
20
+ - `PANTHEON_AGENTS_API_TOKEN` (optional): Bearer token for API auth when running `pantheon-agents server` (defaults for `--token`).
21
+
22
+ Command requirements:
23
+
24
+ - `pantheon-agents add-task|delete-task|show-config|show-tasks`: requires `DATABASE_URL`
25
+ - `pantheon-agents run`: requires `DATABASE_URL` + `PANTHEON_API_KEY`
26
+ - `pantheon-agents config`: requires `DATABASE_URL`, and requires `PANTHEON_API_KEY` when `--bootstrap` is enabled (default) or when `--root-branch-id` is not provided
27
+ - `pantheon-agents reconfig`: requires `DATABASE_URL` + `PANTHEON_API_KEY`
28
+ - `pantheon-agents server`: requires `DATABASE_URL`; auth uses `--token` or env `PANTHEON_AGENTS_API_TOKEN` (unless `--no-auth`)
9
29
 
10
- - `DATABASE_URL` (TiDB/MySQL connection string) must be set for all commands that touch the DB.
11
- - `PANTHEON_API_KEY` is required for the runner loop (`pantheon-agents run`) and bootstrap config flows.
30
+ Notes:
12
31
 
13
- ### DB schema / migrations
32
+ - `PANTHEON_API_KEY` is also required for config-related server endpoints/tools that read Pantheon branch output or re-bootstrap configs.
33
+
34
+ ## Show tasks output modes
35
+
36
+ - Default: concise one-line entries.
37
+ - Table output: use `--no-concise`.
38
+ - JSON output: use `--json`.
39
+
40
+ ## DB schema / migrations
14
41
 
15
42
  The schema lives in `packages/agents/src/db/schema/tidb.sql`.
16
43
 
@@ -59,6 +86,15 @@ curl -sS -H "Authorization: Bearer $TOKEN" \\
59
86
 
60
87
  curl -sS -H "Authorization: Bearer $TOKEN" \\
61
88
  -X DELETE http://127.0.0.1:8000/api/v1/agents/<agent>/tasks/<task_id>
89
+
90
+ # Get agent config (includes bootstrap branch output)
91
+ curl -sS -H "Authorization: Bearer $TOKEN" \\
92
+ http://127.0.0.1:8000/api/v1/agents/<agent>/configs/<project_id>
93
+
94
+ # Re-bootstrap agent config (like `pantheon-agents reconfig`)
95
+ curl -sS -H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" \\
96
+ -X POST http://127.0.0.1:8000/api/v1/agents/<agent>/configs/<project_id>/reconfig \\
97
+ -d '{ "role": "reviewer", "skills": ["style-reviewer"] }'
62
98
  ```
63
99
 
64
100
  ### MCP (HTTP+SSE)
@@ -73,4 +109,5 @@ Tools:
73
109
  - `tasks.create`
74
110
  - `tasks.cancel`
75
111
  - `tasks.delete`
76
-
112
+ - `configs.get`
113
+ - `configs.reconfig`
package/dist/index.js CHANGED
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import { createRequire } from "node:module";
3
- import * as process$1 from "node:process";
4
- import { createCommand } from "commander";
3
+ import { Command, createCommand } from "commander";
4
+ import process$1 from "node:process";
5
5
  import * as fs from "node:fs";
6
6
  import path from "node:path";
7
7
  import { multistream, pino, transport } from "pino";
@@ -420,7 +420,7 @@ var require_cli_options = /* @__PURE__ */ __commonJSMin(((exports, module) => {
420
420
 
421
421
  //#endregion
422
422
  //#region package.json
423
- var version$1 = "0.0.10";
423
+ var version$1 = "0.0.11";
424
424
 
425
425
  //#endregion
426
426
  //#region src/schemas/task-list.ts
@@ -9780,38 +9780,37 @@ async function runAgent(name, options, logger) {
9780
9780
  }
9781
9781
  async function configAgent(name, options) {
9782
9782
  const provider = new TaskListTidbProvider(name, pino());
9783
- const previousConfig = await provider.getAgentConfig(options.projectId);
9784
- if (previousConfig) if (previousConfig.role === options.role) {
9785
- console.log(`Agent ${name} already configured as ${options.role} for project ${options.projectId}.`);
9786
- console.log(`Base branch id: ${previousConfig.base_branch_id}`);
9787
- await provider.close();
9788
- return;
9789
- } else {
9790
- console.error(`Agent ${name} already configured as ${previousConfig.role} for project ${options.projectId}.`);
9791
- console.error(`Base branch id: ${previousConfig.base_branch_id}`);
9792
- console.error(`Cannot change role to ${options.role}`);
9793
- await provider.close();
9794
- process.exitCode = 1;
9795
- return;
9796
- }
9797
- console.log(`Configuring agent ${name} as ${options.role} for project ${options.projectId}.`);
9798
- if (!options.rootBranchId) {
9799
- console.log("No root branch id specified, using project root branch.");
9800
- const project = await getPantheonProjectInfo({ projectId: options.projectId });
9801
- if (!project.root_branch_id) {
9802
- console.error(`Project ${options.projectId} has no root branch. Project status is ${project.status}`);
9803
- await provider.close();
9783
+ try {
9784
+ const previousConfig = await provider.getAgentConfig(options.projectId);
9785
+ if (previousConfig) {
9786
+ if (previousConfig.role === options.role) {
9787
+ console.log(`Agent ${name} already configured as ${options.role} for project ${options.projectId}.`);
9788
+ console.log(`Base branch id: ${previousConfig.base_branch_id}`);
9789
+ return;
9790
+ }
9791
+ console.error(`Agent ${name} already configured as ${previousConfig.role} for project ${options.projectId}.`);
9792
+ console.error(`Base branch id: ${previousConfig.base_branch_id}`);
9793
+ console.error(`Cannot change role to ${options.role}`);
9804
9794
  process.exitCode = 1;
9805
9795
  return;
9806
9796
  }
9807
- options.rootBranchId = project.root_branch_id;
9808
- }
9809
- if (options.bootstrap) {
9810
- const branchId = await executeOnPantheon({
9811
- projectId: options.projectId,
9812
- branchId: options.rootBranchId,
9813
- agent: "codex",
9814
- prompt: `You must follow these instructions":
9797
+ console.log(`Configuring agent ${name} as ${options.role} for project ${options.projectId}.`);
9798
+ if (!options.rootBranchId) {
9799
+ console.log("No root branch id specified, using project root branch.");
9800
+ const project = await getPantheonProjectInfo({ projectId: options.projectId });
9801
+ if (!project.root_branch_id) {
9802
+ console.error(`Project ${options.projectId} has no root branch. Project status is ${project.status}`);
9803
+ process.exitCode = 1;
9804
+ return;
9805
+ }
9806
+ options.rootBranchId = project.root_branch_id;
9807
+ }
9808
+ if (options.bootstrap) {
9809
+ const branchId = await executeOnPantheon({
9810
+ projectId: options.projectId,
9811
+ branchId: options.rootBranchId,
9812
+ agent: "codex",
9813
+ prompt: `You must follow these instructions":
9815
9814
  1. Clone the main branch from ${options.prototypeUrl} to a temporary directory (<pantheon-agents> in follow instructions references to this directory).
9816
9815
  2. Copy the <pantheon-agents>/agents/${options.role}/AGENTS.md to \`<workspace>/AGENTS.md\`.
9817
9816
  3. Copy the <pantheon-agents>/agents/${options.role}/skills directory to \`<workspace>/.codex/skills\` if the source directory exists.
@@ -9825,58 +9824,61 @@ Validate <workspace>: check if files and directorys exists:
9825
9824
 
9826
9825
  Finally, outputs the first 5 lines of <workspace>/AGENTS.md and the skills list in <workspace>/.codex/skills
9827
9826
  `
9828
- });
9829
- let retried = 0;
9830
- const maxRetries = 3;
9831
- let i = 0;
9832
- console.log(`Bootstrap branch created: ${branchId}. Waiting for ready... [poll interval = 10s]`);
9833
- while (true) {
9834
- await new Promise((resolve) => {
9835
- setTimeout(resolve, 1e4);
9836
9827
  });
9837
- const result = await getPantheonBranch({
9838
- branchId,
9839
- projectId: options.projectId,
9840
- getOutputIfFinished: true
9841
- }).then((result) => {
9842
- retried = 0;
9843
- return result;
9844
- }).catch((reason) => {
9845
- if (retried < maxRetries) {
9846
- retried++;
9847
- return { state: "others" };
9848
- } else throw new Error(`Failed to get bootstrap branch status. Retry ${retried} times. Last error: ${getErrorMessage(reason)}`);
9828
+ let retried = 0;
9829
+ const maxRetries = 3;
9830
+ let i = 0;
9831
+ console.log(`Bootstrap branch created: ${branchId}. Waiting for ready... [poll interval = 10s]`);
9832
+ while (true) {
9833
+ await new Promise((resolve) => {
9834
+ setTimeout(resolve, 1e4);
9835
+ });
9836
+ const result = await getPantheonBranch({
9837
+ branchId,
9838
+ projectId: options.projectId,
9839
+ getOutputIfFinished: true
9840
+ }).then((result) => {
9841
+ retried = 0;
9842
+ return result;
9843
+ }).catch((reason) => {
9844
+ if (retried < maxRetries) {
9845
+ retried++;
9846
+ return { state: "others" };
9847
+ }
9848
+ throw new Error(`Failed to get bootstrap branch status. Retry ${retried} times. Last error: ${getErrorMessage(reason)}`);
9849
+ });
9850
+ if (result.state === "failed") {
9851
+ console.error("Bootstrap failed: " + result.error);
9852
+ process.exitCode = 1;
9853
+ return;
9854
+ }
9855
+ if (result.state === "succeed") {
9856
+ console.log("Bootstrap succeeded. Output is:");
9857
+ console.log(result.output);
9858
+ break;
9859
+ }
9860
+ console.log(`Bootstrap in progress... [${++i}]`);
9861
+ }
9862
+ await provider.setAgentConfig({
9863
+ project_id: options.projectId,
9864
+ base_branch_id: branchId,
9865
+ execute_agent: options.executeAgent,
9866
+ role: options.role,
9867
+ skills: options.skills,
9868
+ prototype_url: options.prototypeUrl
9849
9869
  });
9850
- if (result.state === "failed") {
9851
- console.error("Bootstrap failed: " + result.error);
9852
- await provider.close();
9853
- process.exit(1);
9854
- }
9855
- if (result.state === "succeed") {
9856
- console.log("Bootstrap succeeded. Output is:");
9857
- console.log(result.output);
9858
- break;
9859
- }
9860
- console.log(`Bootstrap in progress... [${++i}]`);
9861
- }
9862
- await provider.setAgentConfig({
9870
+ } else await provider.setAgentConfig({
9863
9871
  project_id: options.projectId,
9864
- base_branch_id: branchId,
9872
+ base_branch_id: options.rootBranchId,
9865
9873
  execute_agent: options.executeAgent,
9866
9874
  role: options.role,
9867
9875
  skills: options.skills,
9868
9876
  prototype_url: options.prototypeUrl
9869
9877
  });
9870
- } else await provider.setAgentConfig({
9871
- project_id: options.projectId,
9872
- base_branch_id: options.rootBranchId,
9873
- execute_agent: options.executeAgent,
9874
- role: options.role,
9875
- skills: options.skills,
9876
- prototype_url: options.prototypeUrl
9877
- });
9878
- console.log(`Agent ${name} configured successfully.`);
9879
- await provider.close();
9878
+ console.log(`Agent ${name} configured successfully.`);
9879
+ } finally {
9880
+ await provider.close();
9881
+ }
9880
9882
  }
9881
9883
  async function reconfigAgentWithDeps(name, options, deps) {
9882
9884
  const previousConfig = await deps.provider.getAgentConfig(options.projectId);
@@ -9939,15 +9941,18 @@ async function reconfigAgent(name, options) {
9939
9941
  }
9940
9942
  async function addTask(name, options) {
9941
9943
  const provider = new TaskListTidbProvider(name, pino());
9942
- const config = await provider.getAgentConfig(options.projectId);
9943
- if (!config) throw new Error(`Agent ${name} not configured for project ${options.projectId}`);
9944
- const task = await provider.createTask({
9945
- task: options.prompt,
9946
- project_id: options.projectId,
9947
- base_branch_id: config.base_branch_id
9948
- });
9949
- console.log(`Task ${task.task} queued successfully.`);
9950
- await provider.close();
9944
+ try {
9945
+ const config = await provider.getAgentConfig(options.projectId);
9946
+ if (!config) throw new Error(`Agent ${name} not configured for project ${options.projectId}`);
9947
+ const task = await provider.createTask({
9948
+ task: options.prompt,
9949
+ project_id: options.projectId,
9950
+ base_branch_id: config.base_branch_id
9951
+ });
9952
+ console.log(`Queued task ${task.id} successfully.`);
9953
+ } finally {
9954
+ await provider.close();
9955
+ }
9951
9956
  }
9952
9957
  async function deleteTask(agentName, taskId) {
9953
9958
  const provider = new TaskListTidbProvider(agentName, pino());
@@ -10102,11 +10107,22 @@ async function assertsSingleton(logger, pidFile) {
10102
10107
  }
10103
10108
  }
10104
10109
 
10110
+ //#endregion
10111
+ //#region src/cli/utils/env.ts
10112
+ function ensureEnv(keys) {
10113
+ const missing = keys.filter((key) => !process.env[key]);
10114
+ if (missing.length === 0) return true;
10115
+ for (const key of missing) console.error(`${key} environment variable is not set.`);
10116
+ process.exitCode = 1;
10117
+ return false;
10118
+ }
10119
+
10105
10120
  //#endregion
10106
10121
  //#region src/cli/commands/add-task.ts
10107
10122
  function createAddTaskCommand(version) {
10108
- return createCommand("pantheon-agents 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.").action(async function() {
10123
+ 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.").action(async function() {
10109
10124
  const [name, projectId, taskPrompt] = this.args;
10125
+ if (!ensureEnv(["DATABASE_URL"])) return;
10110
10126
  await addTask(name, {
10111
10127
  projectId,
10112
10128
  prompt: taskPrompt
@@ -10117,12 +10133,16 @@ function createAddTaskCommand(version) {
10117
10133
  //#endregion
10118
10134
  //#region src/cli/commands/config.ts
10119
10135
  function createConfigAgentCommand(version) {
10120
- return createCommand("pantheon-agents 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() {
10136
+ 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() {
10121
10137
  const [name, role, projectId] = this.args;
10138
+ const options = this.opts();
10139
+ const requiredEnvVars = ["DATABASE_URL"];
10140
+ if (options.bootstrap || !options.rootBranchId) requiredEnvVars.push("PANTHEON_API_KEY");
10141
+ if (!ensureEnv(requiredEnvVars)) return;
10122
10142
  await configAgent(name, {
10123
10143
  role,
10124
10144
  projectId,
10125
- ...this.opts()
10145
+ ...options
10126
10146
  });
10127
10147
  });
10128
10148
  }
@@ -10130,8 +10150,9 @@ function createConfigAgentCommand(version) {
10130
10150
  //#endregion
10131
10151
  //#region src/cli/commands/delete-task.ts
10132
10152
  function createDeleteTaskCommand(version) {
10133
- return createCommand("pantheon-agents delete-task").version(version).description("Delete a task for an agent").argument("<name>", "The name of the agent.").argument("<task-id>", "The id of the task.").action(async function() {
10153
+ return createCommand("delete-task").version(version).description("Delete a task for an agent").argument("<name>", "The name of the agent.").argument("<task-id>", "The id of the task.").action(async function() {
10134
10154
  const [name, taskId] = this.args;
10155
+ if (!ensureEnv(["DATABASE_URL"])) return;
10135
10156
  const rl = readline.createInterface({
10136
10157
  input: process$1.stdin,
10137
10158
  output: process$1.stdout
@@ -10139,16 +10160,19 @@ function createDeleteTaskCommand(version) {
10139
10160
  try {
10140
10161
  if ((await rl.question(`Type the task id (${taskId}) to confirm deletion: `)).trim() !== taskId) {
10141
10162
  console.error("Confirmation failed. Task id did not match.");
10142
- process$1.exit(1);
10163
+ process$1.exitCode = 1;
10164
+ return;
10143
10165
  }
10144
10166
  if ((await rl.question("Type DELETE to permanently remove this task: ")).trim() !== "DELETE") {
10145
10167
  console.error("Confirmation failed. Aborting deletion.");
10146
- process$1.exit(1);
10168
+ process$1.exitCode = 1;
10169
+ return;
10147
10170
  }
10148
10171
  const deletedTask = await deleteTask(name, taskId);
10149
10172
  if (!deletedTask) {
10150
10173
  console.error(`Task ${taskId} not found for agent ${name}.`);
10151
- process$1.exit(1);
10174
+ process$1.exitCode = 1;
10175
+ return;
10152
10176
  }
10153
10177
  console.log(`Deleted task ${taskId} for agent ${name}. Status was ${deletedTask.status}.`);
10154
10178
  } finally {
@@ -10177,33 +10201,31 @@ function parseUniqueCommaList(value) {
10177
10201
  //#endregion
10178
10202
  //#region src/cli/commands/reconfig.ts
10179
10203
  function createReconfigAgentCommand(version) {
10180
- return createCommand("pantheon-agents 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() {
10204
+ 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() {
10181
10205
  const [name, projectId] = this.args;
10182
10206
  const options = this.opts();
10207
+ if (!ensureEnv(["DATABASE_URL", "PANTHEON_API_KEY"])) return;
10183
10208
  const resolvedRole = options.role?.trim();
10184
10209
  if (options.role != null && !resolvedRole) {
10185
10210
  console.error("--role must be non-empty.");
10186
- process$1.exit(1);
10187
- }
10188
- try {
10189
- await reconfigAgent(name, {
10190
- projectId,
10191
- role: resolvedRole,
10192
- skills: options.skills
10193
- });
10194
- } catch (error) {
10195
- console.error(getErrorMessage(error));
10196
- process$1.exit(1);
10211
+ process$1.exitCode = 1;
10212
+ return;
10197
10213
  }
10214
+ await reconfigAgent(name, {
10215
+ projectId,
10216
+ role: resolvedRole,
10217
+ skills: options.skills
10218
+ });
10198
10219
  });
10199
10220
  }
10200
10221
 
10201
10222
  //#endregion
10202
10223
  //#region src/cli/commands/run.ts
10203
10224
  function createRunAgentCommand(version) {
10204
- return createCommand("pantheon-agents run").version(version).description("Start a pantheon agents").argument("<name>", "The name of the agent.").option("--data-dir [dir]", "Data directory.", expandTilde("~/.pantheon-agents")).option("--mcp-port", "The port of the MCP server. Defaults to a random port.").option("--loop-interval <seconds>", "The interval of the loop in seconds. Defaults to 5.", (val) => parseInt(val, 10), 5).action(async function() {
10225
+ return createCommand("run").version(version).description("Start a pantheon agent").argument("<name>", "The name of the agent.").option("--data-dir [dir]", "Data directory.", expandTilde("~/.pantheon-agents")).option("--loop-interval <seconds>", "The interval of the loop in seconds. Defaults to 5.", (val) => parseInt(val, 10), 5).action(async function() {
10205
10226
  const [name] = this.args;
10206
10227
  const options = this.opts();
10228
+ if (!ensureEnv(["DATABASE_URL", "PANTHEON_API_KEY"])) return;
10207
10229
  const logFileTransport = transport({
10208
10230
  target: "pino-roll",
10209
10231
  options: {
@@ -21793,6 +21815,141 @@ function taskToDTO(agent, task) {
21793
21815
  error: task.status === "failed" ? task.error : null
21794
21816
  };
21795
21817
  }
21818
+ function agentConfigToDTO(config) {
21819
+ return {
21820
+ agent: config.agent,
21821
+ project_id: config.project_id,
21822
+ base_branch_id: config.base_branch_id,
21823
+ role: config.role,
21824
+ skills: config.skills,
21825
+ prototype_url: config.prototype_url,
21826
+ execute_agent: config.execute_agent
21827
+ };
21828
+ }
21829
+
21830
+ //#endregion
21831
+ //#region src/server/agent-config.ts
21832
+ function assertPantheonApiKeyConfigured() {
21833
+ if (!process.env.PANTHEON_API_KEY?.trim()) throw new ApiError({
21834
+ status: 503,
21835
+ code: "pantheon_api_key_missing",
21836
+ message: "Missing PANTHEON_API_KEY. This endpoint requires Pantheon API access."
21837
+ });
21838
+ }
21839
+ async function getBranchOutputOrThrow(options) {
21840
+ let state;
21841
+ try {
21842
+ state = await options.getPantheonBranchFn({
21843
+ projectId: options.projectId,
21844
+ branchId: options.branchId,
21845
+ getOutputIfFinished: true,
21846
+ manifestingAsSucceed: true
21847
+ });
21848
+ } catch (error) {
21849
+ throw new ApiError({
21850
+ status: 502,
21851
+ code: "pantheon_api_error",
21852
+ message: `Failed to fetch Pantheon branch output for ${options.branchId}.`,
21853
+ details: {
21854
+ project_id: options.projectId,
21855
+ branch_id: options.branchId,
21856
+ error: getErrorMessage(error)
21857
+ }
21858
+ });
21859
+ }
21860
+ if (state.state === "succeed") return state.output ?? "";
21861
+ if (state.state === "failed") throw new ApiError({
21862
+ status: 502,
21863
+ code: "pantheon_branch_failed",
21864
+ message: `Pantheon branch ${options.branchId} failed.`,
21865
+ details: {
21866
+ project_id: options.projectId,
21867
+ branch_id: options.branchId,
21868
+ error: state.error
21869
+ }
21870
+ });
21871
+ throw new ApiError({
21872
+ status: 502,
21873
+ code: "pantheon_branch_not_ready",
21874
+ message: `Pantheon branch ${options.branchId} is not finished yet.`,
21875
+ details: {
21876
+ project_id: options.projectId,
21877
+ branch_id: options.branchId
21878
+ }
21879
+ });
21880
+ }
21881
+ async function getAgentConfigWithBootstrapOutput(options) {
21882
+ assertPantheonApiKeyConfigured();
21883
+ const config = await options.provider.getAgentConfig(options.projectId);
21884
+ if (!config) throw new ApiError({
21885
+ status: 404,
21886
+ code: "config_not_found",
21887
+ message: `No config found for agent ${options.agent} and project ${options.projectId}.`
21888
+ });
21889
+ const bootstrapOutput = await getBranchOutputOrThrow({
21890
+ projectId: config.project_id,
21891
+ branchId: config.base_branch_id,
21892
+ getPantheonBranchFn: options.getPantheonBranchFn ?? getPantheonBranch
21893
+ });
21894
+ return {
21895
+ config: agentConfigToDTO(config),
21896
+ bootstrap_output: bootstrapOutput
21897
+ };
21898
+ }
21899
+ async function reconfigAgentConfigWithBootstrapOutput(options) {
21900
+ assertPantheonApiKeyConfigured();
21901
+ const getPantheonBranchFn = options.getPantheonBranchFn ?? getPantheonBranch;
21902
+ const wrappedGetPantheonBranch = (args) => getPantheonBranchFn({
21903
+ ...args,
21904
+ manifestingAsSucceed: true
21905
+ });
21906
+ let result;
21907
+ try {
21908
+ result = await reconfigAgentWithDeps(options.agent, options.reconfig, {
21909
+ provider: options.provider,
21910
+ executeOnPantheonFn: options.executeOnPantheonFn ?? executeOnPantheon,
21911
+ getPantheonBranchFn: wrappedGetPantheonBranch,
21912
+ sleep: options.sleep,
21913
+ pollIntervalMs: options.pollIntervalMs,
21914
+ maxRetries: options.maxRetries
21915
+ });
21916
+ } catch (error) {
21917
+ if (error instanceof ApiError) throw error;
21918
+ const message = getErrorMessage(error);
21919
+ if (message.startsWith("No config found for agent ")) throw new ApiError({
21920
+ status: 404,
21921
+ code: "config_not_found",
21922
+ message
21923
+ });
21924
+ if (message === "--role must be non-empty.") throw new ApiError({
21925
+ status: 400,
21926
+ code: "validation_error",
21927
+ message
21928
+ });
21929
+ if (message.startsWith("Bootstrap failed:")) throw new ApiError({
21930
+ status: 502,
21931
+ code: "bootstrap_failed",
21932
+ message
21933
+ });
21934
+ throw new ApiError({
21935
+ status: 502,
21936
+ code: "reconfig_failed",
21937
+ message,
21938
+ details: { error: message }
21939
+ });
21940
+ }
21941
+ const updated = await options.provider.getAgentConfig(result.previousConfig.project_id);
21942
+ if (!updated) throw new ApiError({
21943
+ status: 500,
21944
+ code: "config_update_lost",
21945
+ message: "Config disappeared after reconfig update."
21946
+ });
21947
+ return {
21948
+ previous_config: agentConfigToDTO(result.previousConfig),
21949
+ config: agentConfigToDTO(updated),
21950
+ bootstrap_output: result.bootstrapOutput
21951
+ };
21952
+ }
21796
21953
 
21797
21954
  //#endregion
21798
21955
  //#region src/server/api.ts
@@ -21831,6 +21988,10 @@ const createTaskBodySchema = z$1.object({
21831
21988
  project_id: z$1.string().min(1),
21832
21989
  task: z$1.string().min(1)
21833
21990
  });
21991
+ const reconfigBodySchema = z$1.object({
21992
+ role: z$1.string().trim().min(1).optional(),
21993
+ skills: z$1.array(z$1.string().trim().min(1)).optional()
21994
+ });
21834
21995
  function getAgentParam(value) {
21835
21996
  if (typeof value !== "string" || value.trim().length === 0) throw new ApiError({
21836
21997
  status: 400,
@@ -21839,6 +22000,14 @@ function getAgentParam(value) {
21839
22000
  });
21840
22001
  return value;
21841
22002
  }
22003
+ function getProjectIdParam(value) {
22004
+ if (typeof value !== "string" || value.trim().length === 0) throw new ApiError({
22005
+ status: 400,
22006
+ code: "validation_error",
22007
+ message: "Invalid project_id parameter."
22008
+ });
22009
+ return value;
22010
+ }
21842
22011
  function getTaskIdParam(value) {
21843
22012
  if (typeof value !== "string" || value.trim().length === 0) throw new ApiError({
21844
22013
  status: 400,
@@ -21854,6 +22023,31 @@ function createApiRouter(options) {
21854
22023
  const agents = await new TaskListTidbProvider("server", logger, { db }).listAgentNames();
21855
22024
  res.json({ agents });
21856
22025
  }));
22026
+ router.get("/agents/:agent/configs/:project_id", asyncHandler(async (req, res) => {
22027
+ const agent = getAgentParam(req.params.agent);
22028
+ const result = await getAgentConfigWithBootstrapOutput({
22029
+ agent,
22030
+ projectId: getProjectIdParam(req.params.project_id),
22031
+ provider: new TaskListTidbProvider(agent, logger, { db })
22032
+ });
22033
+ res.json(result);
22034
+ }));
22035
+ router.post("/agents/:agent/configs/:project_id/reconfig", asyncHandler(async (req, res) => {
22036
+ const agent = getAgentParam(req.params.agent);
22037
+ const projectId = getProjectIdParam(req.params.project_id);
22038
+ const body = reconfigBodySchema.parse(req.body);
22039
+ const provider = new TaskListTidbProvider(agent, logger, { db });
22040
+ const result = await reconfigAgentConfigWithBootstrapOutput({
22041
+ agent,
22042
+ reconfig: {
22043
+ projectId,
22044
+ role: body.role,
22045
+ skills: body.skills
22046
+ },
22047
+ provider
22048
+ });
22049
+ res.json(result);
22050
+ }));
21857
22051
  router.get("/agents/:agent/tasks", asyncHandler(async (req, res) => {
21858
22052
  const agent = getAgentParam(req.params.agent);
21859
22053
  const query = listTasksQuerySchema.parse(req.query);
@@ -31721,6 +31915,51 @@ function createAgentsMcpServer(options) {
31721
31915
  structuredContent: { deleted: true }
31722
31916
  };
31723
31917
  });
31918
+ server.registerTool("configs.get", {
31919
+ description: "Get an agent's config for a project, including the bootstrap branch output.",
31920
+ inputSchema: {
31921
+ agent: z$1.string().min(1),
31922
+ project_id: z$1.string().min(1)
31923
+ }
31924
+ }, async ({ agent, project_id }) => {
31925
+ return {
31926
+ content: [{
31927
+ type: "text",
31928
+ text: "OK"
31929
+ }],
31930
+ structuredContent: await getAgentConfigWithBootstrapOutput({
31931
+ agent,
31932
+ projectId: project_id,
31933
+ provider: new TaskListTidbProvider(agent, options.logger, { db: options.db })
31934
+ })
31935
+ };
31936
+ });
31937
+ server.registerTool("configs.reconfig", {
31938
+ description: "Re-bootstrap an existing agent configuration for a project (like `pantheon-agents reconfig`).",
31939
+ inputSchema: {
31940
+ agent: z$1.string().min(1),
31941
+ project_id: z$1.string().min(1),
31942
+ role: z$1.string().trim().min(1).optional(),
31943
+ skills: z$1.array(z$1.string().trim().min(1)).optional()
31944
+ }
31945
+ }, async ({ agent, project_id, role, skills }) => {
31946
+ const provider = new TaskListTidbProvider(agent, options.logger, { db: options.db });
31947
+ return {
31948
+ content: [{
31949
+ type: "text",
31950
+ text: "OK"
31951
+ }],
31952
+ structuredContent: await reconfigAgentConfigWithBootstrapOutput({
31953
+ agent,
31954
+ reconfig: {
31955
+ projectId: project_id,
31956
+ role,
31957
+ skills
31958
+ },
31959
+ provider
31960
+ })
31961
+ };
31962
+ });
31724
31963
  return server;
31725
31964
  }
31726
31965
 
@@ -31831,11 +32070,13 @@ async function startAgentsServer(options, logger, overrides = {}) {
31831
32070
  //#endregion
31832
32071
  //#region src/cli/commands/server.ts
31833
32072
  function createServerCommand(version) {
31834
- return createCommand("pantheon-agents server").version(version).description("Start the task HTTP API + MCP server").option("--host <host>", "Bind host. Defaults to 127.0.0.1.", "127.0.0.1").option("--port <port>", "Bind port. Defaults to 8000.", (v) => parseInt(v, 10), 8e3).option("--base-path <path>", "Mount base path prefix. Defaults to /.", "/").option("--token <token>", "Bearer token for API auth. Defaults to env PANTHEON_AGENTS_API_TOKEN.").option("--no-auth", "Disable auth (NOT recommended).").action(async function() {
32073
+ return createCommand("server").version(version).description("Start the task HTTP API + MCP server").option("--host <host>", "Bind host. Defaults to 127.0.0.1.", "127.0.0.1").option("--port <port>", "Bind port. Defaults to 8000.", (v) => parseInt(v, 10), 8e3).option("--base-path <path>", "Mount base path prefix. Defaults to /.", "/").option("--token <token>", "Bearer token for API auth. Defaults to env PANTHEON_AGENTS_API_TOKEN.").option("--no-auth", "Disable auth (NOT recommended).").action(async function() {
31835
32074
  const options = this.opts();
32075
+ if (!ensureEnv(["DATABASE_URL"])) return;
31836
32076
  if (!Number.isInteger(options.port) || options.port <= 0) {
31837
32077
  console.error("Invalid --port value. Must be a positive integer.");
31838
- process$1.exit(1);
32078
+ process$1.exitCode = 1;
32079
+ return;
31839
32080
  }
31840
32081
  const logger = pino({
31841
32082
  timestamp: pino.stdTimeFunctions.isoTime,
@@ -31858,14 +32099,16 @@ function createServerCommand(version) {
31858
32099
  //#endregion
31859
32100
  //#region src/cli/commands/show-config.ts
31860
32101
  function createShowConfigCommand(version) {
31861
- return createCommand("pantheon-agents 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() {
32102
+ 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() {
31862
32103
  const [name, projectId] = this.args;
31863
32104
  const options = this.opts();
32105
+ if (!ensureEnv(["DATABASE_URL"])) return;
31864
32106
  if (projectId) {
31865
32107
  const config = await showAgentConfig(name, projectId);
31866
32108
  if (!config) {
31867
32109
  console.error(`No config found for agent ${name} and project ${projectId}.`);
31868
- process$1.exit(1);
32110
+ process$1.exitCode = 1;
32111
+ return;
31869
32112
  }
31870
32113
  if (options.json) {
31871
32114
  console.log(JSON.stringify(config, null, 2));
@@ -31885,7 +32128,8 @@ function createShowConfigCommand(version) {
31885
32128
  const configs = await showAgentConfigs(name);
31886
32129
  if (!configs.length) {
31887
32130
  console.error(`No configs found for agent ${name}.`);
31888
- process$1.exit(1);
32131
+ process$1.exitCode = 1;
32132
+ return;
31889
32133
  }
31890
32134
  if (options.json) {
31891
32135
  console.log(JSON.stringify(configs, null, 2));
@@ -31922,46 +32166,50 @@ const orderDirections = ["asc", "desc"];
31922
32166
  //#endregion
31923
32167
  //#region src/cli/commands/show-tasks.ts
31924
32168
  function createShowTasksCommand(version) {
31925
- return createCommand("pantheon-agents show-tasks").version(version).description("Show tasks for an agent").argument("[name]", "The name of the agent.").option("--agents <names>", "Comma-separated agent names to query in one call.", parseCommaList, []).option("--all", "Show tasks for all agents.").option("--status <status>", "Filter tasks by status. Multiple values are separated by comma.", parseCommaList, []).option("--order-by <field>", "Order by queued_at, started_at, or ended_at.", "queued_at").option("--order-direction <direction>", "Order direction: asc or desc.", "desc").option("--limit <number>", "Limit the number of tasks shown.", (val) => parseInt(val, 10)).option("--max-task-length <number>", "Maximum task length for table output.", (val) => parseInt(val, 10), 120).option("--full-task", "Do not truncate task text in output.").option("--concise", "Output concise one-line entries.", true).option("--no-color", "Disable colored output.").option("--json", "Output tasks as JSON.").action(async function() {
32169
+ return createCommand("show-tasks").version(version).description("Show tasks for an agent").argument("[name]", "The name of the agent.").option("--agents <names>", "Comma-separated agent names to query in one call.", parseCommaList, []).option("--all", "Show tasks for all agents.").option("--status <status>", "Filter tasks by status. Multiple values are separated by comma.", parseCommaList, []).option("--order-by <field>", "Order by queued_at, started_at, or ended_at.", "queued_at").option("--order-direction <direction>", "Order direction: asc or desc.", "desc").option("--limit <number>", "Limit the number of tasks shown.", (val) => parseInt(val, 10)).option("--max-task-length <number>", "Maximum task length for table output.", (val) => parseInt(val, 10), 120).option("--full-task", "Do not truncate task text in output.").option("--no-concise", "Disable concise one-line entries.").option("--no-color", "Disable colored output.").option("--json", "Output tasks as JSON.").action(async function() {
31926
32170
  const [name] = this.args;
31927
32171
  const options = this.opts();
32172
+ if (!ensureEnv(["DATABASE_URL"])) return;
31928
32173
  const agentNames = /* @__PURE__ */ new Set();
31929
32174
  if (name) agentNames.add(name);
31930
32175
  options.agents.forEach((agent) => agentNames.add(agent));
31931
32176
  if (options.all && agentNames.size > 0) {
31932
32177
  console.error("Use either a specific agent name or --all, not both.");
31933
- process$1.exit(1);
32178
+ process$1.exitCode = 1;
32179
+ return;
31934
32180
  }
31935
32181
  if (!options.all && agentNames.size === 0) {
31936
32182
  console.error("Provide an agent name, --agents, or --all.");
31937
- process$1.exit(1);
32183
+ process$1.exitCode = 1;
32184
+ return;
31938
32185
  }
31939
32186
  const invalidStatuses = options.status.filter((status) => !taskStatuses.includes(status));
31940
32187
  if (invalidStatuses.length > 0) {
31941
32188
  console.error(`Invalid status values: ${invalidStatuses.join(", ")}. Allowed: ${taskStatuses.join(", ")}.`);
31942
- process$1.exit(1);
32189
+ process$1.exitCode = 1;
32190
+ return;
31943
32191
  }
31944
32192
  if (!orderByFields.includes(options.orderBy)) {
31945
32193
  console.error(`Invalid order-by value: ${options.orderBy}. Allowed: ${orderByFields.join(", ")}.`);
31946
- process$1.exit(1);
32194
+ process$1.exitCode = 1;
32195
+ return;
31947
32196
  }
31948
32197
  if (!orderDirections.includes(options.orderDirection)) {
31949
32198
  console.error(`Invalid order-direction value: ${options.orderDirection}. Allowed: ${orderDirections.join(", ")}.`);
31950
- process$1.exit(1);
32199
+ process$1.exitCode = 1;
32200
+ return;
31951
32201
  }
31952
32202
  if (options.limit != null && Number.isNaN(options.limit)) {
31953
32203
  console.error("Invalid limit value. Must be a number.");
31954
- process$1.exit(1);
32204
+ process$1.exitCode = 1;
32205
+ return;
31955
32206
  }
31956
32207
  if (Number.isNaN(options.maxTaskLength) || options.maxTaskLength <= 0) {
31957
32208
  console.error("Invalid max-task-length value. Must be a positive number.");
31958
- process$1.exit(1);
32209
+ process$1.exitCode = 1;
32210
+ return;
31959
32211
  }
31960
32212
  if (options.fullTask && options.maxTaskLength) options.maxTaskLength = Number.POSITIVE_INFINITY;
31961
- if (options.json && options.concise) {
31962
- console.error("Use either --json or --concise, not both.");
31963
- process$1.exit(1);
31964
- }
31965
32213
  const limitedTasks = await showTasksForAgents({
31966
32214
  agents: Array.from(agentNames),
31967
32215
  allAgents: options.all,
@@ -31997,37 +32245,22 @@ function createShowTasksCommand(version) {
31997
32245
  });
31998
32246
  }
31999
32247
 
32000
- //#endregion
32001
- //#region src/cli/utils/env.ts
32002
- function warnIfMissingEnv(keys) {
32003
- for (const key of keys) if (!process.env[key]) console.error(`${key} environment variable is not set.`);
32004
- }
32005
-
32006
32248
  //#endregion
32007
32249
  //#region src/cli/index.ts
32008
- warnIfMissingEnv(["PANTHEON_API_KEY", "DATABASE_URL"]);
32009
- const commands = {
32010
- "add-task": createAddTaskCommand(version$1),
32011
- config: createConfigAgentCommand(version$1),
32012
- "delete-task": createDeleteTaskCommand(version$1),
32013
- reconfig: createReconfigAgentCommand(version$1),
32014
- run: createRunAgentCommand(version$1),
32015
- server: createServerCommand(version$1),
32016
- "show-config": createShowConfigCommand(version$1),
32017
- "show-tasks": createShowTasksCommand(version$1)
32018
- };
32019
- function printCommandHelpAndExit(command) {
32020
- console.error(`Invalid command: ${command}. Supported commands: ${Object.keys(commands).join(", ")}.`);
32021
- console.error(" Run pantheon-agents help <command> for more information.");
32022
- process$1.exit(1);
32023
- }
32024
- if (process$1.argv[2] === "help") {
32025
- const command = process$1.argv[3];
32026
- if (command in commands) commands[command].help({ error: false });
32027
- else printCommandHelpAndExit(command);
32028
- }
32029
- if (!commands[process$1.argv[2]]) printCommandHelpAndExit(process$1.argv[2]);
32030
- commands[process$1.argv[2]].parse(process$1.argv.slice(3), { from: "user" });
32250
+ 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(createReconfigAgentCommand(version$1)).addCommand(createRunAgentCommand(version$1)).addCommand(createServerCommand(version$1)).addCommand(createShowConfigCommand(version$1)).addCommand(createShowTasksCommand(version$1));
32251
+ async function main() {
32252
+ if (process$1.argv.length <= 2) {
32253
+ program.outputHelp();
32254
+ return;
32255
+ }
32256
+ await program.parseAsync(process$1.argv);
32257
+ }
32258
+ try {
32259
+ await main();
32260
+ } catch (error) {
32261
+ console.error(getErrorMessage(error));
32262
+ process$1.exitCode = 1;
32263
+ }
32031
32264
 
32032
32265
  //#endregion
32033
32266
  export { };
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@pantheon.ai/agents",
3
3
  "type": "module",
4
- "version": "0.0.10",
4
+ "version": "0.0.11",
5
5
  "bin": {
6
6
  "pantheon-agents": "dist/index.js"
7
7
  },