@pantheon.ai/agents 0.0.9 → 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.
- package/README.md +43 -6
- package/dist/index.js +540 -142
- package/package.json +2 -1
package/README.md
CHANGED
|
@@ -1,16 +1,43 @@
|
|
|
1
1
|
# `@pantheon.ai/agents`
|
|
2
2
|
|
|
3
|
-
This package
|
|
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
|
-
##
|
|
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
|
-
|
|
11
|
-
- `PANTHEON_API_KEY` is required for the runner loop (`pantheon-agents run`) and bootstrap config flows.
|
|
30
|
+
Notes:
|
|
12
31
|
|
|
13
|
-
|
|
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
|
|
4
|
-
import
|
|
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.
|
|
423
|
+
var version$1 = "0.0.11";
|
|
424
424
|
|
|
425
425
|
//#endregion
|
|
426
426
|
//#region src/schemas/task-list.ts
|
|
@@ -611,6 +611,13 @@ var TaskListTidbProvider = class extends TaskListProvider {
|
|
|
611
611
|
...config
|
|
612
612
|
}).execute();
|
|
613
613
|
}
|
|
614
|
+
async updateAgentConfig({ skills, ...config }) {
|
|
615
|
+
const result = await this.db.updateTable("agent_project_config").set({
|
|
616
|
+
skills: JSON.stringify(skills),
|
|
617
|
+
...config
|
|
618
|
+
}).where("agent", "=", this.agentName).where("project_id", "=", config.project_id).executeTakeFirst();
|
|
619
|
+
if (Number(result.numUpdatedRows ?? 0) === 0) throw new Error(`No config found to update for agent ${this.agentName} and project ${config.project_id}.`);
|
|
620
|
+
}
|
|
614
621
|
async getTask(taskId) {
|
|
615
622
|
const taskItem = await this.selectTask().where("id", "=", taskId).executeTakeFirst();
|
|
616
623
|
if (taskItem == null) return null;
|
|
@@ -9701,6 +9708,69 @@ async function startPendingTask(provider, task, logger) {
|
|
|
9701
9708
|
|
|
9702
9709
|
//#endregion
|
|
9703
9710
|
//#region src/core/index.ts
|
|
9711
|
+
function normalizeSkills(value) {
|
|
9712
|
+
if (Array.isArray(value)) return value.filter((item) => typeof item === "string");
|
|
9713
|
+
if (typeof value === "string") {
|
|
9714
|
+
try {
|
|
9715
|
+
const parsed = JSON.parse(value);
|
|
9716
|
+
if (Array.isArray(parsed)) return parsed.filter((item) => typeof item === "string");
|
|
9717
|
+
} catch {}
|
|
9718
|
+
return value.split(",").map((item) => item.trim()).filter((item) => item !== "");
|
|
9719
|
+
}
|
|
9720
|
+
return [];
|
|
9721
|
+
}
|
|
9722
|
+
function buildRebootstrapDiffPrompt(options) {
|
|
9723
|
+
return `You must follow these instructions":
|
|
9724
|
+
1. Clone the main branch from ${options.prototypeUrl} to a temporary directory (<pantheon-agents> in follow instructions references to this directory).
|
|
9725
|
+
2. Copy the <pantheon-agents>/agents/${options.role}/AGENTS.md to \`<workspace>/AGENTS.md\`.
|
|
9726
|
+
3. Copy the <pantheon-agents>/agents/${options.role}/skills directory to \`<workspace>/.codex/skills\` if the source directory exists.
|
|
9727
|
+
${options.skills.length > 0 ? `4. Copy the pantheon-agents/skills/\{${options.skills.join(",")}} directory to \`<workspace>/.codex/skills/\`` : ""}
|
|
9728
|
+
|
|
9729
|
+
Validate <workspace>: check if files and directorys exists:
|
|
9730
|
+
- AGENTS.md
|
|
9731
|
+
- .codex/skills
|
|
9732
|
+
|
|
9733
|
+
**You MUST NOT generate AGENTS.md and skills by yourself if clone failed.**
|
|
9734
|
+
|
|
9735
|
+
Finally, output a DIFF-oriented summary (not raw file contents) between:
|
|
9736
|
+
- BEFORE: the workspace state before applying the steps above (previous base branch id: ${options.previousBaseBranchId})
|
|
9737
|
+
- AFTER: the workspace state after applying the steps above
|
|
9738
|
+
|
|
9739
|
+
The summary MUST include:
|
|
9740
|
+
- \`AGENTS.md\` changes (diff/stat only; do NOT print full file content)
|
|
9741
|
+
- \`.codex/skills\` added/removed/modified entries (filenames only; do NOT print file contents)
|
|
9742
|
+
- A concise summary with: changed files list + add/remove/modify counts
|
|
9743
|
+
`;
|
|
9744
|
+
}
|
|
9745
|
+
async function defaultSleep(ms) {
|
|
9746
|
+
await new Promise((resolve) => {
|
|
9747
|
+
setTimeout(resolve, ms);
|
|
9748
|
+
});
|
|
9749
|
+
}
|
|
9750
|
+
async function waitForPantheonBranchOutput(options) {
|
|
9751
|
+
let retried = 0;
|
|
9752
|
+
let i = 0;
|
|
9753
|
+
while (true) {
|
|
9754
|
+
await options.sleep(options.pollIntervalMs);
|
|
9755
|
+
const result = await options.getPantheonBranchFn({
|
|
9756
|
+
branchId: options.branchId,
|
|
9757
|
+
projectId: options.projectId,
|
|
9758
|
+
getOutputIfFinished: true
|
|
9759
|
+
}).then((result) => {
|
|
9760
|
+
retried = 0;
|
|
9761
|
+
return result;
|
|
9762
|
+
}).catch((reason) => {
|
|
9763
|
+
if (retried < options.maxRetries) {
|
|
9764
|
+
retried++;
|
|
9765
|
+
return { state: "others" };
|
|
9766
|
+
}
|
|
9767
|
+
throw new Error(`Failed to get bootstrap branch status. Retry ${retried} times. Last error: ${getErrorMessage(reason)}`);
|
|
9768
|
+
});
|
|
9769
|
+
if (result.state === "failed") throw new Error(`Bootstrap failed: ${result.error}`);
|
|
9770
|
+
if (result.state === "succeed") return result.output ?? "";
|
|
9771
|
+
console.log(`Bootstrap in progress... [${++i}]`);
|
|
9772
|
+
}
|
|
9773
|
+
}
|
|
9704
9774
|
async function runAgent(name, options, logger) {
|
|
9705
9775
|
const agentDir = path.join(options.dataDir, "agents", name);
|
|
9706
9776
|
const pidFile = path.join(agentDir, "pid");
|
|
@@ -9710,38 +9780,37 @@ async function runAgent(name, options, logger) {
|
|
|
9710
9780
|
}
|
|
9711
9781
|
async function configAgent(name, options) {
|
|
9712
9782
|
const provider = new TaskListTidbProvider(name, pino());
|
|
9713
|
-
|
|
9714
|
-
|
|
9715
|
-
|
|
9716
|
-
|
|
9717
|
-
|
|
9718
|
-
|
|
9719
|
-
|
|
9720
|
-
|
|
9721
|
-
|
|
9722
|
-
|
|
9723
|
-
|
|
9724
|
-
process.exitCode = 1;
|
|
9725
|
-
return;
|
|
9726
|
-
}
|
|
9727
|
-
console.log(`Configuring agent ${name} as ${options.role} for project ${options.projectId}.`);
|
|
9728
|
-
if (!options.rootBranchId) {
|
|
9729
|
-
console.log("No root branch id specified, using project root branch.");
|
|
9730
|
-
const project = await getPantheonProjectInfo({ projectId: options.projectId });
|
|
9731
|
-
if (!project.root_branch_id) {
|
|
9732
|
-
console.error(`Project ${options.projectId} has no root branch. Project status is ${project.status}`);
|
|
9733
|
-
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}`);
|
|
9734
9794
|
process.exitCode = 1;
|
|
9735
9795
|
return;
|
|
9736
9796
|
}
|
|
9737
|
-
options.
|
|
9738
|
-
|
|
9739
|
-
|
|
9740
|
-
|
|
9741
|
-
|
|
9742
|
-
|
|
9743
|
-
|
|
9744
|
-
|
|
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":
|
|
9745
9814
|
1. Clone the main branch from ${options.prototypeUrl} to a temporary directory (<pantheon-agents> in follow instructions references to this directory).
|
|
9746
9815
|
2. Copy the <pantheon-agents>/agents/${options.role}/AGENTS.md to \`<workspace>/AGENTS.md\`.
|
|
9747
9816
|
3. Copy the <pantheon-agents>/agents/${options.role}/skills directory to \`<workspace>/.codex/skills\` if the source directory exists.
|
|
@@ -9755,70 +9824,135 @@ Validate <workspace>: check if files and directorys exists:
|
|
|
9755
9824
|
|
|
9756
9825
|
Finally, outputs the first 5 lines of <workspace>/AGENTS.md and the skills list in <workspace>/.codex/skills
|
|
9757
9826
|
`
|
|
9758
|
-
});
|
|
9759
|
-
let retried = 0;
|
|
9760
|
-
const maxRetries = 3;
|
|
9761
|
-
let i = 0;
|
|
9762
|
-
console.log(`Bootstrap branch created: ${branchId}. Waiting for ready... [poll interval = 10s]`);
|
|
9763
|
-
while (true) {
|
|
9764
|
-
await new Promise((resolve) => {
|
|
9765
|
-
setTimeout(resolve, 1e4);
|
|
9766
9827
|
});
|
|
9767
|
-
|
|
9768
|
-
|
|
9769
|
-
|
|
9770
|
-
|
|
9771
|
-
|
|
9772
|
-
|
|
9773
|
-
|
|
9774
|
-
|
|
9775
|
-
|
|
9776
|
-
|
|
9777
|
-
|
|
9778
|
-
|
|
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
|
|
9779
9869
|
});
|
|
9780
|
-
|
|
9781
|
-
console.error("Bootstrap failed: " + result.error);
|
|
9782
|
-
await provider.close();
|
|
9783
|
-
process.exit(1);
|
|
9784
|
-
}
|
|
9785
|
-
if (result.state === "succeed") {
|
|
9786
|
-
console.log("Bootstrap succeeded. Output is:");
|
|
9787
|
-
console.log(result.output);
|
|
9788
|
-
break;
|
|
9789
|
-
}
|
|
9790
|
-
console.log(`Bootstrap in progress... [${++i}]`);
|
|
9791
|
-
}
|
|
9792
|
-
await provider.setAgentConfig({
|
|
9870
|
+
} else await provider.setAgentConfig({
|
|
9793
9871
|
project_id: options.projectId,
|
|
9794
|
-
base_branch_id:
|
|
9872
|
+
base_branch_id: options.rootBranchId,
|
|
9795
9873
|
execute_agent: options.executeAgent,
|
|
9796
9874
|
role: options.role,
|
|
9797
9875
|
skills: options.skills,
|
|
9798
9876
|
prototype_url: options.prototypeUrl
|
|
9799
9877
|
});
|
|
9800
|
-
|
|
9878
|
+
console.log(`Agent ${name} configured successfully.`);
|
|
9879
|
+
} finally {
|
|
9880
|
+
await provider.close();
|
|
9881
|
+
}
|
|
9882
|
+
}
|
|
9883
|
+
async function reconfigAgentWithDeps(name, options, deps) {
|
|
9884
|
+
const previousConfig = await deps.provider.getAgentConfig(options.projectId);
|
|
9885
|
+
if (!previousConfig) throw new Error(`No config found for agent ${name} and project ${options.projectId}.`);
|
|
9886
|
+
const resolvedRole = options.role?.trim() || previousConfig.role;
|
|
9887
|
+
if (options.role != null && !options.role.trim()) throw new Error("--role must be non-empty.");
|
|
9888
|
+
const resolvedSkills = options.skills ?? normalizeSkills(previousConfig.skills);
|
|
9889
|
+
const nextBaseBranchId = await deps.executeOnPantheonFn({
|
|
9890
|
+
projectId: options.projectId,
|
|
9891
|
+
branchId: previousConfig.base_branch_id,
|
|
9892
|
+
agent: "codex",
|
|
9893
|
+
prompt: buildRebootstrapDiffPrompt({
|
|
9894
|
+
prototypeUrl: previousConfig.prototype_url,
|
|
9895
|
+
role: resolvedRole,
|
|
9896
|
+
skills: resolvedSkills,
|
|
9897
|
+
previousBaseBranchId: previousConfig.base_branch_id
|
|
9898
|
+
})
|
|
9899
|
+
});
|
|
9900
|
+
console.log(`Bootstrap branch created: ${nextBaseBranchId}. Waiting for ready... [poll interval = ${Math.floor((deps.pollIntervalMs ?? 1e4) / 1e3)}s]`);
|
|
9901
|
+
const bootstrapOutput = await waitForPantheonBranchOutput({
|
|
9902
|
+
projectId: options.projectId,
|
|
9903
|
+
branchId: nextBaseBranchId,
|
|
9904
|
+
pollIntervalMs: deps.pollIntervalMs ?? 1e4,
|
|
9905
|
+
maxRetries: deps.maxRetries ?? 3,
|
|
9906
|
+
getPantheonBranchFn: deps.getPantheonBranchFn,
|
|
9907
|
+
sleep: deps.sleep ?? defaultSleep
|
|
9908
|
+
});
|
|
9909
|
+
await deps.provider.updateAgentConfig({
|
|
9801
9910
|
project_id: options.projectId,
|
|
9802
|
-
base_branch_id:
|
|
9803
|
-
execute_agent:
|
|
9804
|
-
role:
|
|
9805
|
-
skills:
|
|
9806
|
-
prototype_url:
|
|
9911
|
+
base_branch_id: nextBaseBranchId,
|
|
9912
|
+
execute_agent: previousConfig.execute_agent,
|
|
9913
|
+
role: resolvedRole,
|
|
9914
|
+
skills: resolvedSkills,
|
|
9915
|
+
prototype_url: previousConfig.prototype_url
|
|
9807
9916
|
});
|
|
9808
|
-
|
|
9809
|
-
|
|
9917
|
+
return {
|
|
9918
|
+
previousConfig,
|
|
9919
|
+
resolvedRole,
|
|
9920
|
+
resolvedSkills,
|
|
9921
|
+
nextBaseBranchId,
|
|
9922
|
+
bootstrapOutput
|
|
9923
|
+
};
|
|
9924
|
+
}
|
|
9925
|
+
async function reconfigAgent(name, options) {
|
|
9926
|
+
const provider = new TaskListTidbProvider(name, pino());
|
|
9927
|
+
try {
|
|
9928
|
+
console.log(`Reconfiguring agent ${name} for project ${options.projectId}.`);
|
|
9929
|
+
const result = await reconfigAgentWithDeps(name, options, {
|
|
9930
|
+
provider,
|
|
9931
|
+
executeOnPantheonFn: executeOnPantheon,
|
|
9932
|
+
getPantheonBranchFn: getPantheonBranch
|
|
9933
|
+
});
|
|
9934
|
+
console.log("Re-bootstrap succeeded. Output is:");
|
|
9935
|
+
console.log(result.bootstrapOutput);
|
|
9936
|
+
console.log(`New base branch id: ${result.nextBaseBranchId}`);
|
|
9937
|
+
console.log(`Agent ${name} reconfigured successfully.`);
|
|
9938
|
+
} finally {
|
|
9939
|
+
await provider.close();
|
|
9940
|
+
}
|
|
9810
9941
|
}
|
|
9811
9942
|
async function addTask(name, options) {
|
|
9812
9943
|
const provider = new TaskListTidbProvider(name, pino());
|
|
9813
|
-
|
|
9814
|
-
|
|
9815
|
-
|
|
9816
|
-
task
|
|
9817
|
-
|
|
9818
|
-
|
|
9819
|
-
|
|
9820
|
-
|
|
9821
|
-
|
|
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
|
+
}
|
|
9822
9956
|
}
|
|
9823
9957
|
async function deleteTask(agentName, taskId) {
|
|
9824
9958
|
const provider = new TaskListTidbProvider(agentName, pino());
|
|
@@ -9973,11 +10107,22 @@ async function assertsSingleton(logger, pidFile) {
|
|
|
9973
10107
|
}
|
|
9974
10108
|
}
|
|
9975
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
|
+
|
|
9976
10120
|
//#endregion
|
|
9977
10121
|
//#region src/cli/commands/add-task.ts
|
|
9978
10122
|
function createAddTaskCommand(version) {
|
|
9979
|
-
return createCommand("
|
|
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() {
|
|
9980
10124
|
const [name, projectId, taskPrompt] = this.args;
|
|
10125
|
+
if (!ensureEnv(["DATABASE_URL"])) return;
|
|
9981
10126
|
await addTask(name, {
|
|
9982
10127
|
projectId,
|
|
9983
10128
|
prompt: taskPrompt
|
|
@@ -9988,12 +10133,16 @@ function createAddTaskCommand(version) {
|
|
|
9988
10133
|
//#endregion
|
|
9989
10134
|
//#region src/cli/commands/config.ts
|
|
9990
10135
|
function createConfigAgentCommand(version) {
|
|
9991
|
-
return createCommand("
|
|
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() {
|
|
9992
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;
|
|
9993
10142
|
await configAgent(name, {
|
|
9994
10143
|
role,
|
|
9995
10144
|
projectId,
|
|
9996
|
-
...
|
|
10145
|
+
...options
|
|
9997
10146
|
});
|
|
9998
10147
|
});
|
|
9999
10148
|
}
|
|
@@ -10001,8 +10150,9 @@ function createConfigAgentCommand(version) {
|
|
|
10001
10150
|
//#endregion
|
|
10002
10151
|
//#region src/cli/commands/delete-task.ts
|
|
10003
10152
|
function createDeleteTaskCommand(version) {
|
|
10004
|
-
return createCommand("
|
|
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() {
|
|
10005
10154
|
const [name, taskId] = this.args;
|
|
10155
|
+
if (!ensureEnv(["DATABASE_URL"])) return;
|
|
10006
10156
|
const rl = readline.createInterface({
|
|
10007
10157
|
input: process$1.stdin,
|
|
10008
10158
|
output: process$1.stdout
|
|
@@ -10010,16 +10160,19 @@ function createDeleteTaskCommand(version) {
|
|
|
10010
10160
|
try {
|
|
10011
10161
|
if ((await rl.question(`Type the task id (${taskId}) to confirm deletion: `)).trim() !== taskId) {
|
|
10012
10162
|
console.error("Confirmation failed. Task id did not match.");
|
|
10013
|
-
process$1.
|
|
10163
|
+
process$1.exitCode = 1;
|
|
10164
|
+
return;
|
|
10014
10165
|
}
|
|
10015
10166
|
if ((await rl.question("Type DELETE to permanently remove this task: ")).trim() !== "DELETE") {
|
|
10016
10167
|
console.error("Confirmation failed. Aborting deletion.");
|
|
10017
|
-
process$1.
|
|
10168
|
+
process$1.exitCode = 1;
|
|
10169
|
+
return;
|
|
10018
10170
|
}
|
|
10019
10171
|
const deletedTask = await deleteTask(name, taskId);
|
|
10020
10172
|
if (!deletedTask) {
|
|
10021
10173
|
console.error(`Task ${taskId} not found for agent ${name}.`);
|
|
10022
|
-
process$1.
|
|
10174
|
+
process$1.exitCode = 1;
|
|
10175
|
+
return;
|
|
10023
10176
|
}
|
|
10024
10177
|
console.log(`Deleted task ${taskId} for agent ${name}. Status was ${deletedTask.status}.`);
|
|
10025
10178
|
} finally {
|
|
@@ -10028,12 +10181,51 @@ function createDeleteTaskCommand(version) {
|
|
|
10028
10181
|
});
|
|
10029
10182
|
}
|
|
10030
10183
|
|
|
10184
|
+
//#endregion
|
|
10185
|
+
//#region src/cli/utils/parse.ts
|
|
10186
|
+
function parseCommaList(value) {
|
|
10187
|
+
return value.split(",").map((item) => item.trim()).filter((item) => item !== "");
|
|
10188
|
+
}
|
|
10189
|
+
function parseUniqueCommaList(value) {
|
|
10190
|
+
const items = parseCommaList(value);
|
|
10191
|
+
const seen = /* @__PURE__ */ new Set();
|
|
10192
|
+
const result = [];
|
|
10193
|
+
for (const item of items) {
|
|
10194
|
+
if (seen.has(item)) continue;
|
|
10195
|
+
seen.add(item);
|
|
10196
|
+
result.push(item);
|
|
10197
|
+
}
|
|
10198
|
+
return result;
|
|
10199
|
+
}
|
|
10200
|
+
|
|
10201
|
+
//#endregion
|
|
10202
|
+
//#region src/cli/commands/reconfig.ts
|
|
10203
|
+
function createReconfigAgentCommand(version) {
|
|
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() {
|
|
10205
|
+
const [name, projectId] = this.args;
|
|
10206
|
+
const options = this.opts();
|
|
10207
|
+
if (!ensureEnv(["DATABASE_URL", "PANTHEON_API_KEY"])) return;
|
|
10208
|
+
const resolvedRole = options.role?.trim();
|
|
10209
|
+
if (options.role != null && !resolvedRole) {
|
|
10210
|
+
console.error("--role must be non-empty.");
|
|
10211
|
+
process$1.exitCode = 1;
|
|
10212
|
+
return;
|
|
10213
|
+
}
|
|
10214
|
+
await reconfigAgent(name, {
|
|
10215
|
+
projectId,
|
|
10216
|
+
role: resolvedRole,
|
|
10217
|
+
skills: options.skills
|
|
10218
|
+
});
|
|
10219
|
+
});
|
|
10220
|
+
}
|
|
10221
|
+
|
|
10031
10222
|
//#endregion
|
|
10032
10223
|
//#region src/cli/commands/run.ts
|
|
10033
10224
|
function createRunAgentCommand(version) {
|
|
10034
|
-
return createCommand("
|
|
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() {
|
|
10035
10226
|
const [name] = this.args;
|
|
10036
10227
|
const options = this.opts();
|
|
10228
|
+
if (!ensureEnv(["DATABASE_URL", "PANTHEON_API_KEY"])) return;
|
|
10037
10229
|
const logFileTransport = transport({
|
|
10038
10230
|
target: "pino-roll",
|
|
10039
10231
|
options: {
|
|
@@ -21623,6 +21815,141 @@ function taskToDTO(agent, task) {
|
|
|
21623
21815
|
error: task.status === "failed" ? task.error : null
|
|
21624
21816
|
};
|
|
21625
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
|
+
}
|
|
21626
21953
|
|
|
21627
21954
|
//#endregion
|
|
21628
21955
|
//#region src/server/api.ts
|
|
@@ -21661,6 +21988,10 @@ const createTaskBodySchema = z$1.object({
|
|
|
21661
21988
|
project_id: z$1.string().min(1),
|
|
21662
21989
|
task: z$1.string().min(1)
|
|
21663
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
|
+
});
|
|
21664
21995
|
function getAgentParam(value) {
|
|
21665
21996
|
if (typeof value !== "string" || value.trim().length === 0) throw new ApiError({
|
|
21666
21997
|
status: 400,
|
|
@@ -21669,6 +22000,14 @@ function getAgentParam(value) {
|
|
|
21669
22000
|
});
|
|
21670
22001
|
return value;
|
|
21671
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
|
+
}
|
|
21672
22011
|
function getTaskIdParam(value) {
|
|
21673
22012
|
if (typeof value !== "string" || value.trim().length === 0) throw new ApiError({
|
|
21674
22013
|
status: 400,
|
|
@@ -21684,6 +22023,31 @@ function createApiRouter(options) {
|
|
|
21684
22023
|
const agents = await new TaskListTidbProvider("server", logger, { db }).listAgentNames();
|
|
21685
22024
|
res.json({ agents });
|
|
21686
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
|
+
}));
|
|
21687
22051
|
router.get("/agents/:agent/tasks", asyncHandler(async (req, res) => {
|
|
21688
22052
|
const agent = getAgentParam(req.params.agent);
|
|
21689
22053
|
const query = listTasksQuerySchema.parse(req.query);
|
|
@@ -31551,6 +31915,51 @@ function createAgentsMcpServer(options) {
|
|
|
31551
31915
|
structuredContent: { deleted: true }
|
|
31552
31916
|
};
|
|
31553
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
|
+
});
|
|
31554
31963
|
return server;
|
|
31555
31964
|
}
|
|
31556
31965
|
|
|
@@ -31661,11 +32070,13 @@ async function startAgentsServer(options, logger, overrides = {}) {
|
|
|
31661
32070
|
//#endregion
|
|
31662
32071
|
//#region src/cli/commands/server.ts
|
|
31663
32072
|
function createServerCommand(version) {
|
|
31664
|
-
return createCommand("
|
|
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() {
|
|
31665
32074
|
const options = this.opts();
|
|
32075
|
+
if (!ensureEnv(["DATABASE_URL"])) return;
|
|
31666
32076
|
if (!Number.isInteger(options.port) || options.port <= 0) {
|
|
31667
32077
|
console.error("Invalid --port value. Must be a positive integer.");
|
|
31668
|
-
process$1.
|
|
32078
|
+
process$1.exitCode = 1;
|
|
32079
|
+
return;
|
|
31669
32080
|
}
|
|
31670
32081
|
const logger = pino({
|
|
31671
32082
|
timestamp: pino.stdTimeFunctions.isoTime,
|
|
@@ -31688,14 +32099,16 @@ function createServerCommand(version) {
|
|
|
31688
32099
|
//#endregion
|
|
31689
32100
|
//#region src/cli/commands/show-config.ts
|
|
31690
32101
|
function createShowConfigCommand(version) {
|
|
31691
|
-
return createCommand("
|
|
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() {
|
|
31692
32103
|
const [name, projectId] = this.args;
|
|
31693
32104
|
const options = this.opts();
|
|
32105
|
+
if (!ensureEnv(["DATABASE_URL"])) return;
|
|
31694
32106
|
if (projectId) {
|
|
31695
32107
|
const config = await showAgentConfig(name, projectId);
|
|
31696
32108
|
if (!config) {
|
|
31697
32109
|
console.error(`No config found for agent ${name} and project ${projectId}.`);
|
|
31698
|
-
process$1.
|
|
32110
|
+
process$1.exitCode = 1;
|
|
32111
|
+
return;
|
|
31699
32112
|
}
|
|
31700
32113
|
if (options.json) {
|
|
31701
32114
|
console.log(JSON.stringify(config, null, 2));
|
|
@@ -31715,7 +32128,8 @@ function createShowConfigCommand(version) {
|
|
|
31715
32128
|
const configs = await showAgentConfigs(name);
|
|
31716
32129
|
if (!configs.length) {
|
|
31717
32130
|
console.error(`No configs found for agent ${name}.`);
|
|
31718
|
-
process$1.
|
|
32131
|
+
process$1.exitCode = 1;
|
|
32132
|
+
return;
|
|
31719
32133
|
}
|
|
31720
32134
|
if (options.json) {
|
|
31721
32135
|
console.log(JSON.stringify(configs, null, 2));
|
|
@@ -31749,55 +32163,53 @@ const orderByFields = [
|
|
|
31749
32163
|
];
|
|
31750
32164
|
const orderDirections = ["asc", "desc"];
|
|
31751
32165
|
|
|
31752
|
-
//#endregion
|
|
31753
|
-
//#region src/cli/utils/parse.ts
|
|
31754
|
-
function parseCommaList(value) {
|
|
31755
|
-
return value.split(",").map((item) => item.trim()).filter((item) => item !== "");
|
|
31756
|
-
}
|
|
31757
|
-
|
|
31758
32166
|
//#endregion
|
|
31759
32167
|
//#region src/cli/commands/show-tasks.ts
|
|
31760
32168
|
function createShowTasksCommand(version) {
|
|
31761
|
-
return createCommand("
|
|
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() {
|
|
31762
32170
|
const [name] = this.args;
|
|
31763
32171
|
const options = this.opts();
|
|
32172
|
+
if (!ensureEnv(["DATABASE_URL"])) return;
|
|
31764
32173
|
const agentNames = /* @__PURE__ */ new Set();
|
|
31765
32174
|
if (name) agentNames.add(name);
|
|
31766
32175
|
options.agents.forEach((agent) => agentNames.add(agent));
|
|
31767
32176
|
if (options.all && agentNames.size > 0) {
|
|
31768
32177
|
console.error("Use either a specific agent name or --all, not both.");
|
|
31769
|
-
process$1.
|
|
32178
|
+
process$1.exitCode = 1;
|
|
32179
|
+
return;
|
|
31770
32180
|
}
|
|
31771
32181
|
if (!options.all && agentNames.size === 0) {
|
|
31772
32182
|
console.error("Provide an agent name, --agents, or --all.");
|
|
31773
|
-
process$1.
|
|
32183
|
+
process$1.exitCode = 1;
|
|
32184
|
+
return;
|
|
31774
32185
|
}
|
|
31775
32186
|
const invalidStatuses = options.status.filter((status) => !taskStatuses.includes(status));
|
|
31776
32187
|
if (invalidStatuses.length > 0) {
|
|
31777
32188
|
console.error(`Invalid status values: ${invalidStatuses.join(", ")}. Allowed: ${taskStatuses.join(", ")}.`);
|
|
31778
|
-
process$1.
|
|
32189
|
+
process$1.exitCode = 1;
|
|
32190
|
+
return;
|
|
31779
32191
|
}
|
|
31780
32192
|
if (!orderByFields.includes(options.orderBy)) {
|
|
31781
32193
|
console.error(`Invalid order-by value: ${options.orderBy}. Allowed: ${orderByFields.join(", ")}.`);
|
|
31782
|
-
process$1.
|
|
32194
|
+
process$1.exitCode = 1;
|
|
32195
|
+
return;
|
|
31783
32196
|
}
|
|
31784
32197
|
if (!orderDirections.includes(options.orderDirection)) {
|
|
31785
32198
|
console.error(`Invalid order-direction value: ${options.orderDirection}. Allowed: ${orderDirections.join(", ")}.`);
|
|
31786
|
-
process$1.
|
|
32199
|
+
process$1.exitCode = 1;
|
|
32200
|
+
return;
|
|
31787
32201
|
}
|
|
31788
32202
|
if (options.limit != null && Number.isNaN(options.limit)) {
|
|
31789
32203
|
console.error("Invalid limit value. Must be a number.");
|
|
31790
|
-
process$1.
|
|
32204
|
+
process$1.exitCode = 1;
|
|
32205
|
+
return;
|
|
31791
32206
|
}
|
|
31792
32207
|
if (Number.isNaN(options.maxTaskLength) || options.maxTaskLength <= 0) {
|
|
31793
32208
|
console.error("Invalid max-task-length value. Must be a positive number.");
|
|
31794
|
-
process$1.
|
|
32209
|
+
process$1.exitCode = 1;
|
|
32210
|
+
return;
|
|
31795
32211
|
}
|
|
31796
32212
|
if (options.fullTask && options.maxTaskLength) options.maxTaskLength = Number.POSITIVE_INFINITY;
|
|
31797
|
-
if (options.json && options.concise) {
|
|
31798
|
-
console.error("Use either --json or --concise, not both.");
|
|
31799
|
-
process$1.exit(1);
|
|
31800
|
-
}
|
|
31801
32213
|
const limitedTasks = await showTasksForAgents({
|
|
31802
32214
|
agents: Array.from(agentNames),
|
|
31803
32215
|
allAgents: options.all,
|
|
@@ -31833,36 +32245,22 @@ function createShowTasksCommand(version) {
|
|
|
31833
32245
|
});
|
|
31834
32246
|
}
|
|
31835
32247
|
|
|
31836
|
-
//#endregion
|
|
31837
|
-
//#region src/cli/utils/env.ts
|
|
31838
|
-
function warnIfMissingEnv(keys) {
|
|
31839
|
-
for (const key of keys) if (!process.env[key]) console.error(`${key} environment variable is not set.`);
|
|
31840
|
-
}
|
|
31841
|
-
|
|
31842
32248
|
//#endregion
|
|
31843
32249
|
//#region src/cli/index.ts
|
|
31844
|
-
|
|
31845
|
-
|
|
31846
|
-
|
|
31847
|
-
|
|
31848
|
-
|
|
31849
|
-
|
|
31850
|
-
|
|
31851
|
-
|
|
31852
|
-
|
|
31853
|
-
|
|
31854
|
-
|
|
31855
|
-
console.error(
|
|
31856
|
-
|
|
31857
|
-
|
|
31858
|
-
}
|
|
31859
|
-
if (process$1.argv[2] === "help") {
|
|
31860
|
-
const command = process$1.argv[3];
|
|
31861
|
-
if (command in commands) commands[command].help({ error: false });
|
|
31862
|
-
else printCommandHelpAndExit(command);
|
|
31863
|
-
}
|
|
31864
|
-
if (!commands[process$1.argv[2]]) printCommandHelpAndExit(process$1.argv[2]);
|
|
31865
|
-
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
|
+
}
|
|
31866
32264
|
|
|
31867
32265
|
//#endregion
|
|
31868
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.
|
|
4
|
+
"version": "0.0.11",
|
|
5
5
|
"bin": {
|
|
6
6
|
"pantheon-agents": "dist/index.js"
|
|
7
7
|
},
|
|
@@ -13,6 +13,7 @@
|
|
|
13
13
|
},
|
|
14
14
|
"scripts": {
|
|
15
15
|
"build": "rolldown -c rolldown.config.ts && chmod +x dist/index.js",
|
|
16
|
+
"test": "bun test",
|
|
16
17
|
"dev:db:start": "tiup playground --without-monitor --tag pantheon-agents",
|
|
17
18
|
"dev:db:gen": "kysely-codegen "
|
|
18
19
|
},
|