@pantheon.ai/agents 0.3.1 → 0.3.3
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
CHANGED
|
@@ -60,15 +60,19 @@ Example migration added in this version:
|
|
|
60
60
|
|
|
61
61
|
- `src/db/migrations/tidb/20260304_0001_add_task_retry_columns.sql`
|
|
62
62
|
- `src/db/migrations/db9/20260304_0001_add_task_retry_columns.sql`
|
|
63
|
+
- `src/db/migrations/tidb/20260305_0002_add_agent_config_envs.sql`
|
|
64
|
+
- `src/db/migrations/db9/20260305_0002_add_agent_config_envs.sql`
|
|
63
65
|
|
|
64
66
|
Apply manually:
|
|
65
67
|
|
|
66
68
|
```bash
|
|
67
69
|
# TiDB/MySQL
|
|
68
70
|
mysql --host 127.0.0.1 --port 4000 -u root pantheon_agents < src/db/migrations/tidb/20260304_0001_add_task_retry_columns.sql
|
|
71
|
+
mysql --host 127.0.0.1 --port 4000 -u root pantheon_agents < src/db/migrations/tidb/20260305_0002_add_agent_config_envs.sql
|
|
69
72
|
|
|
70
73
|
# db9/PostgreSQL
|
|
71
74
|
psql "$DATABASE_URL" -f src/db/migrations/db9/20260304_0001_add_task_retry_columns.sql
|
|
75
|
+
psql "$DATABASE_URL" -f src/db/migrations/db9/20260305_0002_add_agent_config_envs.sql
|
|
72
76
|
```
|
|
73
77
|
|
|
74
78
|
## Developer Local Setup
|
|
@@ -112,6 +116,9 @@ pantheon-agents config reviewer "Download some files" --project-id 019c0495-f77a
|
|
|
112
116
|
|
|
113
117
|
# update role on later config request
|
|
114
118
|
pantheon-agents config reviewer "Switch to review workflow" --project-id 019c0495-f77a-7b6c-ade0-6b59c6654617 --role reviewer
|
|
119
|
+
|
|
120
|
+
# set per-project branch envs (passed on every task start)
|
|
121
|
+
pantheon-agents config reviewer "Use Anthropic model" --project-id 019c0495-f77a-7b6c-ade0-6b59c6654617 --envs '{"ANTHROPIC_MODEL":"claude-opus-4-5"}'
|
|
115
122
|
```
|
|
116
123
|
|
|
117
124
|
### 2) Add a task
|
|
@@ -159,6 +166,7 @@ pantheon-agents show-tasks --all
|
|
|
159
166
|
pantheon-agents get-task <agent-name> <task-id>
|
|
160
167
|
pantheon-agents cancel-task <agent-name> <task-id> [reason] --yes
|
|
161
168
|
pantheon-agents retry-task <agent-name> <task-id> [reason] --yes
|
|
169
|
+
pantheon-agents kill <agent-name> <task-id> [reason] --yes
|
|
162
170
|
pantheon-agents skill.sh
|
|
163
171
|
pantheon-agents gen-migration-sql --provider tidb --from 0.3.0
|
|
164
172
|
pantheon-agents delete-task <agent-name> <task-id>
|
package/dist/index.js
CHANGED
|
@@ -399,7 +399,7 @@ var require_cli_options = /* @__PURE__ */ __commonJSMin(((exports, module) => {
|
|
|
399
399
|
|
|
400
400
|
//#endregion
|
|
401
401
|
//#region package.json
|
|
402
|
-
var version = "0.3.
|
|
402
|
+
var version = "0.3.3";
|
|
403
403
|
|
|
404
404
|
//#endregion
|
|
405
405
|
//#region src/db/db9.ts
|
|
@@ -639,6 +639,7 @@ var TaskListDb9Provider = class extends TaskListProvider {
|
|
|
639
639
|
"agent_project_config.retry_backoff_seconds",
|
|
640
640
|
"agent_project_config.role",
|
|
641
641
|
"agent_project_config.skills",
|
|
642
|
+
"agent_project_config.envs",
|
|
642
643
|
"agent_project_config.prototype_url",
|
|
643
644
|
"agent_project_config.execute_agent"
|
|
644
645
|
]).select((eb) => eb.ref("task.status").$castTo().as("config_task_status")).where("agent_project_config.project_id", "=", projectId).where("agent_project_config.agent", "=", this.agentName).executeTakeFirst();
|
|
@@ -657,26 +658,30 @@ var TaskListDb9Provider = class extends TaskListProvider {
|
|
|
657
658
|
"agent_project_config.retry_backoff_seconds",
|
|
658
659
|
"agent_project_config.role",
|
|
659
660
|
"agent_project_config.skills",
|
|
661
|
+
"agent_project_config.envs",
|
|
660
662
|
"agent_project_config.prototype_url",
|
|
661
663
|
"agent_project_config.execute_agent"
|
|
662
664
|
]).select((eb) => eb.ref("task.status").$castTo().as("config_task_status")).where("agent_project_config.agent", "=", this.agentName).execute();
|
|
663
665
|
}
|
|
664
|
-
async setAgentConfig({ skills, ...config }) {
|
|
666
|
+
async setAgentConfig({ skills, envs, ...config }) {
|
|
665
667
|
const maxRetryAttempts = config.max_retry_attempts ?? 3;
|
|
666
668
|
const retryBackoffSeconds = config.retry_backoff_seconds ?? 30;
|
|
667
669
|
await this.db.insertInto("agent_project_config").values({
|
|
668
670
|
agent: this.agentName,
|
|
669
671
|
skills: JSON.stringify(skills),
|
|
672
|
+
envs: JSON.stringify(envs ?? {}),
|
|
670
673
|
...config,
|
|
671
674
|
max_retry_attempts: maxRetryAttempts,
|
|
672
675
|
retry_backoff_seconds: retryBackoffSeconds
|
|
673
676
|
}).execute();
|
|
674
677
|
}
|
|
675
|
-
async updateAgentConfig({ skills, ...config }) {
|
|
678
|
+
async updateAgentConfig({ skills, envs, ...config }) {
|
|
676
679
|
const maxRetryAttempts = config.max_retry_attempts;
|
|
677
680
|
const retryBackoffSeconds = config.retry_backoff_seconds;
|
|
681
|
+
const resolvedEnvs = envs == null ? void 0 : JSON.stringify(envs);
|
|
678
682
|
const result = await this.db.updateTable("agent_project_config").set({
|
|
679
683
|
skills: JSON.stringify(skills),
|
|
684
|
+
...resolvedEnvs == null ? {} : { envs: resolvedEnvs },
|
|
680
685
|
...config,
|
|
681
686
|
...maxRetryAttempts == null ? {} : { max_retry_attempts: maxRetryAttempts },
|
|
682
687
|
...retryBackoffSeconds == null ? {} : { retry_backoff_seconds: retryBackoffSeconds }
|
|
@@ -783,22 +788,25 @@ var TaskListTidbProvider = class extends TaskListProvider {
|
|
|
783
788
|
async getAgentConfigs() {
|
|
784
789
|
return await this.db.selectFrom("agent_project_config").innerJoin("task", "task.id", "agent_project_config.config_task_id").selectAll("agent_project_config").select((eb) => eb.ref("task.status").$castTo().as("config_task_status")).where("agent_project_config.agent", "=", this.agentName).execute();
|
|
785
790
|
}
|
|
786
|
-
async setAgentConfig({ skills, ...config }) {
|
|
791
|
+
async setAgentConfig({ skills, envs, ...config }) {
|
|
787
792
|
const maxRetryAttempts = config.max_retry_attempts ?? 3;
|
|
788
793
|
const retryBackoffSeconds = config.retry_backoff_seconds ?? 30;
|
|
789
794
|
await this.db.insertInto("agent_project_config").values({
|
|
790
795
|
agent: this.agentName,
|
|
791
796
|
skills: JSON.stringify(skills),
|
|
797
|
+
envs: JSON.stringify(envs ?? {}),
|
|
792
798
|
...config,
|
|
793
799
|
max_retry_attempts: maxRetryAttempts,
|
|
794
800
|
retry_backoff_seconds: retryBackoffSeconds
|
|
795
801
|
}).execute();
|
|
796
802
|
}
|
|
797
|
-
async updateAgentConfig({ skills, ...config }) {
|
|
803
|
+
async updateAgentConfig({ skills, envs, ...config }) {
|
|
798
804
|
const maxRetryAttempts = config.max_retry_attempts;
|
|
799
805
|
const retryBackoffSeconds = config.retry_backoff_seconds;
|
|
806
|
+
const resolvedEnvs = envs == null ? void 0 : JSON.stringify(envs);
|
|
800
807
|
const result = await this.db.updateTable("agent_project_config").set({
|
|
801
808
|
skills: JSON.stringify(skills),
|
|
809
|
+
...resolvedEnvs == null ? {} : { envs: resolvedEnvs },
|
|
802
810
|
...config,
|
|
803
811
|
...maxRetryAttempts == null ? {} : { max_retry_attempts: maxRetryAttempts },
|
|
804
812
|
...retryBackoffSeconds == null ? {} : { retry_backoff_seconds: retryBackoffSeconds }
|
|
@@ -902,6 +910,20 @@ function normalizeSkills(value) {
|
|
|
902
910
|
}
|
|
903
911
|
return [];
|
|
904
912
|
}
|
|
913
|
+
function normalizeEnvs(value) {
|
|
914
|
+
const parseRecord = (input) => {
|
|
915
|
+
if (!input || typeof input !== "object" || Array.isArray(input)) return {};
|
|
916
|
+
const result = {};
|
|
917
|
+
for (const [key, item] of Object.entries(input)) if (typeof item === "string") result[key] = item;
|
|
918
|
+
return result;
|
|
919
|
+
};
|
|
920
|
+
if (typeof value === "string") try {
|
|
921
|
+
return parseRecord(JSON.parse(value));
|
|
922
|
+
} catch {
|
|
923
|
+
return {};
|
|
924
|
+
}
|
|
925
|
+
return parseRecord(value);
|
|
926
|
+
}
|
|
905
927
|
function buildConfigSetupStep(options) {
|
|
906
928
|
const prototypeRepoUrl = normalizeRepoUrl(options.prototypeRepoUrl);
|
|
907
929
|
const skillsArg = JSON.stringify(options.skills.join(","));
|
|
@@ -1664,7 +1686,9 @@ const projectSchema = z.object({
|
|
|
1664
1686
|
created_at: zodJsonDate,
|
|
1665
1687
|
updated_at: zodJsonDate,
|
|
1666
1688
|
branches_total: z.number().int().nullable().optional(),
|
|
1667
|
-
background_tasks_total: z.number().int().nullable().optional()
|
|
1689
|
+
background_tasks_total: z.number().int().nullable().optional(),
|
|
1690
|
+
env_id: z.string().uuid().nullable().optional(),
|
|
1691
|
+
env_display_name: z.string().nullable().optional()
|
|
1668
1692
|
});
|
|
1669
1693
|
const projectMessageSchema = z.object({
|
|
1670
1694
|
id: z.string(),
|
|
@@ -1785,6 +1809,7 @@ const listProjectBackgroundTasks = defineApi(get`/api/v1/projects/${"projectId"}
|
|
|
1785
1809
|
const createProjectExploration = defineApi(post`/api/v1/projects/${"projectId"}/explorations`, typeOf(), createExplorationResultSchema);
|
|
1786
1810
|
const listProjectExplorations = defineApi(get`/api/v1/projects/${"projectId"}/explorations`, null, explorationSchema.array());
|
|
1787
1811
|
const getProjectExplorationResult = defineApi(get`/api/v1/projects/${"projectId"}/explorations/${"explorationId"}/result`, null, explorationResultSchema);
|
|
1812
|
+
const killProjectBranch = defineApi(post`/api/v1/projects/${"projectId"}/branches/${"branchId"}/kill`, typeOf(), snapSchema.nullable());
|
|
1788
1813
|
const getProjectBranchFs = defineApi(get`/api/v1/projects/${"projectId"}/branches/${"branchId"}/fs/${"path*"}`, null, FSResponseValidator);
|
|
1789
1814
|
const getProjectBranchFsPreview = defineApi(get`/api/v1/projects/${"projectId"}/branches/${"branchId"}/preview/${"path*"}`, null, FSResponseValidator);
|
|
1790
1815
|
|
|
@@ -2073,15 +2098,33 @@ async function getPantheonBranchInfo({ projectId, branchId }) {
|
|
|
2073
2098
|
branchId
|
|
2074
2099
|
}, null);
|
|
2075
2100
|
}
|
|
2076
|
-
async function executeOnPantheon({ projectId, branchId, prompt, agent }) {
|
|
2101
|
+
async function executeOnPantheon({ projectId, branchId, prompt, agent, envs }) {
|
|
2077
2102
|
const promptSequence = Array.isArray(prompt) ? prompt : [prompt];
|
|
2078
2103
|
return (await executor.execute(createProjectExploration, { projectId }, {
|
|
2079
2104
|
shared_prompt_sequence: promptSequence,
|
|
2080
2105
|
num_branches: 1,
|
|
2081
2106
|
agent,
|
|
2082
|
-
parent_branch_id: branchId
|
|
2107
|
+
parent_branch_id: branchId,
|
|
2108
|
+
envs
|
|
2083
2109
|
})).branches[0];
|
|
2084
2110
|
}
|
|
2111
|
+
function isNotFoundError(error) {
|
|
2112
|
+
if (!(error instanceof Error)) return false;
|
|
2113
|
+
const message = error.message.toLowerCase();
|
|
2114
|
+
return message.includes("404") || message.includes("not found");
|
|
2115
|
+
}
|
|
2116
|
+
async function killPantheonBranchExecution({ projectId, branchId }) {
|
|
2117
|
+
try {
|
|
2118
|
+
await executor.execute(killProjectBranch, {
|
|
2119
|
+
projectId,
|
|
2120
|
+
branchId
|
|
2121
|
+
}, {});
|
|
2122
|
+
return [branchId];
|
|
2123
|
+
} catch (error) {
|
|
2124
|
+
if (isNotFoundError(error)) return [];
|
|
2125
|
+
throw error;
|
|
2126
|
+
}
|
|
2127
|
+
}
|
|
2085
2128
|
|
|
2086
2129
|
//#endregion
|
|
2087
2130
|
//#region src/core/task-list.ts
|
|
@@ -2226,6 +2269,7 @@ async function pollRunningTaskState(provider, state, logger) {
|
|
|
2226
2269
|
execute_agent: config.execute_agent,
|
|
2227
2270
|
role: config.role,
|
|
2228
2271
|
skills: normalizeSkills(config.skills),
|
|
2272
|
+
envs: normalizeEnvs(config.envs),
|
|
2229
2273
|
prototype_url: config.prototype_url
|
|
2230
2274
|
});
|
|
2231
2275
|
} else if (newStatus.state === "failed") {
|
|
@@ -2330,7 +2374,8 @@ async function startPendingTask(provider, task, config, logger) {
|
|
|
2330
2374
|
projectId: taskToStart.project_id,
|
|
2331
2375
|
branchId: taskToStart.base_branch_id,
|
|
2332
2376
|
prompt: promptSequence,
|
|
2333
|
-
agent: config.execute_agent
|
|
2377
|
+
agent: config.execute_agent,
|
|
2378
|
+
envs: normalizeEnvs(config.envs)
|
|
2334
2379
|
});
|
|
2335
2380
|
});
|
|
2336
2381
|
logger.info(`Task ${taskToStart.id} started successfully on branch [id = ${taskBranch.branch_id}, snap_id = ${taskBranch.latest_snap_id}]`);
|
|
@@ -2807,6 +2852,7 @@ var WatchStepAggregator = class {
|
|
|
2807
2852
|
//#region src/core/index.ts
|
|
2808
2853
|
const DEFAULT_MAX_RETRY_ATTEMPTS = 3;
|
|
2809
2854
|
const DEFAULT_RETRY_BACKOFF_SECONDS = 30;
|
|
2855
|
+
const DEFAULT_KILL_REASON = "Killed by user";
|
|
2810
2856
|
async function runAgent(name, options, logger) {
|
|
2811
2857
|
const agentDir = path.join(options.dataDir, "agents", name);
|
|
2812
2858
|
const pidFile = path.join(agentDir, "pid");
|
|
@@ -2856,6 +2902,7 @@ async function configAgent(name, options) {
|
|
|
2856
2902
|
return;
|
|
2857
2903
|
}
|
|
2858
2904
|
const resolvedSkills = options.skills ?? normalizeSkills(previousConfig?.skills);
|
|
2905
|
+
const resolvedEnvs = options.envs ?? normalizeEnvs(previousConfig?.envs);
|
|
2859
2906
|
const resolvedExecuteAgent = options.executeAgent.trim() || previousConfig?.execute_agent || "codex";
|
|
2860
2907
|
const resolvedPrototypeUrl = options.prototypeUrl.trim() || previousConfig?.prototype_url || "https://github.com/pingcap-inc/pantheon-agents";
|
|
2861
2908
|
const baseBranchId = previousConfig ? previousConfig.base_branch_id : await resolveInitialBaseBranchId(options.projectId, options.rootBranchId);
|
|
@@ -2890,6 +2937,7 @@ async function configAgent(name, options) {
|
|
|
2890
2937
|
execute_agent: resolvedExecuteAgent,
|
|
2891
2938
|
role: resolvedRole ?? previousConfig.role,
|
|
2892
2939
|
skills: resolvedSkills,
|
|
2940
|
+
envs: resolvedEnvs,
|
|
2893
2941
|
prototype_url: resolvedPrototypeUrl
|
|
2894
2942
|
});
|
|
2895
2943
|
if (!options.prompt) await provider.updateTask({
|
|
@@ -2959,6 +3007,7 @@ async function configAgent(name, options) {
|
|
|
2959
3007
|
execute_agent: resolvedExecuteAgent,
|
|
2960
3008
|
role: resolvedRole,
|
|
2961
3009
|
skills: resolvedSkills,
|
|
3010
|
+
envs: resolvedEnvs,
|
|
2962
3011
|
prototype_url: resolvedPrototypeUrl
|
|
2963
3012
|
});
|
|
2964
3013
|
else await provider.updateAgentConfig({
|
|
@@ -2972,6 +3021,7 @@ async function configAgent(name, options) {
|
|
|
2972
3021
|
execute_agent: resolvedExecuteAgent,
|
|
2973
3022
|
role: resolvedRole ?? previousConfig.role,
|
|
2974
3023
|
skills: resolvedSkills,
|
|
3024
|
+
envs: resolvedEnvs,
|
|
2975
3025
|
prototype_url: resolvedPrototypeUrl
|
|
2976
3026
|
});
|
|
2977
3027
|
for (const pendingTask of pendingTasks) {
|
|
@@ -3064,6 +3114,170 @@ async function retryTask(agentName, taskId, reason) {
|
|
|
3064
3114
|
await provider.close();
|
|
3065
3115
|
}
|
|
3066
3116
|
}
|
|
3117
|
+
function resolveTaskAttemptCount(value) {
|
|
3118
|
+
if (typeof value === "number" && Number.isInteger(value) && value > 0) return value;
|
|
3119
|
+
return 1;
|
|
3120
|
+
}
|
|
3121
|
+
function hasTaskBranchId(task) {
|
|
3122
|
+
return "branch_id" in task && typeof task.branch_id === "string";
|
|
3123
|
+
}
|
|
3124
|
+
function collectKillBranchTargets(tasks, options = {}) {
|
|
3125
|
+
const childrenByParentId = /* @__PURE__ */ new Map();
|
|
3126
|
+
const taskById = /* @__PURE__ */ new Map();
|
|
3127
|
+
for (const task of tasks) {
|
|
3128
|
+
taskById.set(task.id, task);
|
|
3129
|
+
if (!task.parent_task_id) continue;
|
|
3130
|
+
const siblings = childrenByParentId.get(task.parent_task_id);
|
|
3131
|
+
if (siblings) siblings.push(task);
|
|
3132
|
+
else childrenByParentId.set(task.parent_task_id, [task]);
|
|
3133
|
+
}
|
|
3134
|
+
const roots = options.rootTaskIds && options.rootTaskIds.length > 0 ? options.rootTaskIds.map((taskId) => taskById.get(taskId)).filter((task) => task != null) : tasks.filter((task) => task.status === "pending" || task.status === "running");
|
|
3135
|
+
if (roots.length === 0) return [];
|
|
3136
|
+
const maxDepthByTaskId = /* @__PURE__ */ new Map();
|
|
3137
|
+
const stack = roots.map((task) => ({
|
|
3138
|
+
taskId: task.id,
|
|
3139
|
+
depth: 0
|
|
3140
|
+
}));
|
|
3141
|
+
while (stack.length > 0) {
|
|
3142
|
+
const item = stack.pop();
|
|
3143
|
+
if (!item) continue;
|
|
3144
|
+
const previousDepth = maxDepthByTaskId.get(item.taskId);
|
|
3145
|
+
if (previousDepth != null && previousDepth >= item.depth) continue;
|
|
3146
|
+
maxDepthByTaskId.set(item.taskId, item.depth);
|
|
3147
|
+
const children = childrenByParentId.get(item.taskId) ?? [];
|
|
3148
|
+
for (const child of children) stack.push({
|
|
3149
|
+
taskId: child.id,
|
|
3150
|
+
depth: item.depth + 1
|
|
3151
|
+
});
|
|
3152
|
+
}
|
|
3153
|
+
const scoredTargets = [];
|
|
3154
|
+
for (const [taskId, depth] of maxDepthByTaskId) {
|
|
3155
|
+
const task = taskById.get(taskId);
|
|
3156
|
+
if (!task || !hasTaskBranchId(task)) continue;
|
|
3157
|
+
scoredTargets.push({
|
|
3158
|
+
task_id: task.id,
|
|
3159
|
+
project_id: task.project_id,
|
|
3160
|
+
branch_id: task.branch_id,
|
|
3161
|
+
depth,
|
|
3162
|
+
queued_at_ms: task.queued_at.getTime()
|
|
3163
|
+
});
|
|
3164
|
+
}
|
|
3165
|
+
scoredTargets.sort((a, b) => {
|
|
3166
|
+
if (a.depth !== b.depth) return b.depth - a.depth;
|
|
3167
|
+
return b.queued_at_ms - a.queued_at_ms;
|
|
3168
|
+
});
|
|
3169
|
+
const seen = /* @__PURE__ */ new Set();
|
|
3170
|
+
const targets = [];
|
|
3171
|
+
for (const target of scoredTargets) {
|
|
3172
|
+
const key = `${target.project_id}:${target.branch_id}`;
|
|
3173
|
+
if (seen.has(key)) continue;
|
|
3174
|
+
seen.add(key);
|
|
3175
|
+
targets.push({
|
|
3176
|
+
task_id: target.task_id,
|
|
3177
|
+
project_id: target.project_id,
|
|
3178
|
+
branch_id: target.branch_id
|
|
3179
|
+
});
|
|
3180
|
+
}
|
|
3181
|
+
return targets;
|
|
3182
|
+
}
|
|
3183
|
+
function collectTaskSubtree(tasks, rootTaskId) {
|
|
3184
|
+
const childrenByParentId = /* @__PURE__ */ new Map();
|
|
3185
|
+
const taskById = /* @__PURE__ */ new Map();
|
|
3186
|
+
for (const task of tasks) {
|
|
3187
|
+
taskById.set(task.id, task);
|
|
3188
|
+
if (!task.parent_task_id) continue;
|
|
3189
|
+
const siblings = childrenByParentId.get(task.parent_task_id);
|
|
3190
|
+
if (siblings) siblings.push(task);
|
|
3191
|
+
else childrenByParentId.set(task.parent_task_id, [task]);
|
|
3192
|
+
}
|
|
3193
|
+
if (!taskById.has(rootTaskId)) return [];
|
|
3194
|
+
const subtreeIds = /* @__PURE__ */ new Set();
|
|
3195
|
+
const stack = [rootTaskId];
|
|
3196
|
+
while (stack.length > 0) {
|
|
3197
|
+
const taskId = stack.pop();
|
|
3198
|
+
if (!taskId || subtreeIds.has(taskId)) continue;
|
|
3199
|
+
subtreeIds.add(taskId);
|
|
3200
|
+
const children = childrenByParentId.get(taskId) ?? [];
|
|
3201
|
+
for (const child of children) stack.push(child.id);
|
|
3202
|
+
}
|
|
3203
|
+
return tasks.filter((task) => subtreeIds.has(task.id));
|
|
3204
|
+
}
|
|
3205
|
+
function toCancelledTask(task, reason, cancelledAt) {
|
|
3206
|
+
return {
|
|
3207
|
+
status: "cancelled",
|
|
3208
|
+
id: task.id,
|
|
3209
|
+
task: task.task,
|
|
3210
|
+
type: task.type,
|
|
3211
|
+
project_id: task.project_id,
|
|
3212
|
+
base_branch_id: task.base_branch_id,
|
|
3213
|
+
parent_task_id: task.parent_task_id ?? null,
|
|
3214
|
+
config_version: task.config_version ?? null,
|
|
3215
|
+
attempt_count: resolveTaskAttemptCount(task.attempt_count),
|
|
3216
|
+
cancel_reason: reason,
|
|
3217
|
+
queued_at: task.queued_at,
|
|
3218
|
+
cancelled_at: cancelledAt
|
|
3219
|
+
};
|
|
3220
|
+
}
|
|
3221
|
+
async function killAgentTasks(agentName, taskId, reason = DEFAULT_KILL_REASON, deps = {}) {
|
|
3222
|
+
const provider = createTaskListProvider(agentName, pino());
|
|
3223
|
+
try {
|
|
3224
|
+
const tasks = await provider.getTasks({
|
|
3225
|
+
order_by: "queued_at",
|
|
3226
|
+
order_direction: "asc"
|
|
3227
|
+
});
|
|
3228
|
+
const subtreeTasks = collectTaskSubtree(tasks, taskId);
|
|
3229
|
+
if (subtreeTasks.length === 0) return null;
|
|
3230
|
+
const pendingTasks = subtreeTasks.filter((task) => task.status === "pending");
|
|
3231
|
+
const runningTasks = subtreeTasks.filter((task) => task.status === "running");
|
|
3232
|
+
for (const pendingTask of pendingTasks) await provider.cancelTask(pendingTask, reason);
|
|
3233
|
+
const killTargets = collectKillBranchTargets(tasks, { rootTaskIds: [taskId] });
|
|
3234
|
+
const killBranch = deps.killBranch ?? killPantheonBranchExecution;
|
|
3235
|
+
const killedBranches = [];
|
|
3236
|
+
const seenKilledBranchKeys = /* @__PURE__ */ new Set();
|
|
3237
|
+
const failedBranchKills = [];
|
|
3238
|
+
for (const target of killTargets) try {
|
|
3239
|
+
const deletedBranchIds = await killBranch({
|
|
3240
|
+
projectId: target.project_id,
|
|
3241
|
+
branchId: target.branch_id
|
|
3242
|
+
});
|
|
3243
|
+
for (const deletedBranchId of deletedBranchIds) {
|
|
3244
|
+
const branchKey = `${target.project_id}:${deletedBranchId}`;
|
|
3245
|
+
if (seenKilledBranchKeys.has(branchKey)) continue;
|
|
3246
|
+
seenKilledBranchKeys.add(branchKey);
|
|
3247
|
+
killedBranches.push({
|
|
3248
|
+
project_id: target.project_id,
|
|
3249
|
+
branch_id: deletedBranchId
|
|
3250
|
+
});
|
|
3251
|
+
}
|
|
3252
|
+
} catch (error) {
|
|
3253
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
3254
|
+
failedBranchKills.push({
|
|
3255
|
+
task_id: target.task_id,
|
|
3256
|
+
project_id: target.project_id,
|
|
3257
|
+
branch_id: target.branch_id,
|
|
3258
|
+
error: message
|
|
3259
|
+
});
|
|
3260
|
+
}
|
|
3261
|
+
const now = /* @__PURE__ */ new Date();
|
|
3262
|
+
const killedBranchKeys = new Set(killedBranches.map((item) => `${item.project_id}:${item.branch_id}`));
|
|
3263
|
+
let runningCancelledCount = 0;
|
|
3264
|
+
for (const runningTask of runningTasks) {
|
|
3265
|
+
const branchKey = `${runningTask.project_id}:${runningTask.branch_id}`;
|
|
3266
|
+
if (!killedBranchKeys.has(branchKey)) continue;
|
|
3267
|
+
await provider.updateTask(toCancelledTask(runningTask, reason, now));
|
|
3268
|
+
runningCancelledCount++;
|
|
3269
|
+
}
|
|
3270
|
+
return {
|
|
3271
|
+
task_id: taskId,
|
|
3272
|
+
pending_cancelled_count: pendingTasks.length,
|
|
3273
|
+
running_cancelled_count: runningCancelledCount,
|
|
3274
|
+
killed_branches: killedBranches,
|
|
3275
|
+
failed_branch_kills: failedBranchKills
|
|
3276
|
+
};
|
|
3277
|
+
} finally {
|
|
3278
|
+
await provider.close();
|
|
3279
|
+
}
|
|
3280
|
+
}
|
|
3067
3281
|
async function showAgentConfig(agentName, projectId) {
|
|
3068
3282
|
const provider = createTaskListProvider(agentName, pino());
|
|
3069
3283
|
try {
|
|
@@ -3364,8 +3578,25 @@ function parsePositiveInteger(value, fieldName) {
|
|
|
3364
3578
|
if (!Number.isInteger(parsed) || parsed <= 0) throw new InvalidArgumentError(`${fieldName} must be a positive integer.`);
|
|
3365
3579
|
return parsed;
|
|
3366
3580
|
}
|
|
3581
|
+
function parseEnvs(value) {
|
|
3582
|
+
const trimmed = value.trim();
|
|
3583
|
+
if (!trimmed) throw new InvalidArgumentError("envs must be a JSON object of string values.");
|
|
3584
|
+
let parsed;
|
|
3585
|
+
try {
|
|
3586
|
+
parsed = JSON.parse(trimmed);
|
|
3587
|
+
} catch {
|
|
3588
|
+
throw new InvalidArgumentError("envs must be valid JSON.");
|
|
3589
|
+
}
|
|
3590
|
+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) throw new InvalidArgumentError("envs must be a JSON object.");
|
|
3591
|
+
const out = {};
|
|
3592
|
+
for (const [key, item] of Object.entries(parsed)) {
|
|
3593
|
+
if (typeof item !== "string") throw new InvalidArgumentError(`envs.${key} must be a string value.`);
|
|
3594
|
+
out[key] = item;
|
|
3595
|
+
}
|
|
3596
|
+
return out;
|
|
3597
|
+
}
|
|
3367
3598
|
function createConfigAgentCommand(version, deps = {}) {
|
|
3368
|
-
return createCommand("config").version(version).description("Queue a configuration task for an agent/project").argument("<name>", "The name of the agent.").argument("[prompt]", "The configuration task prompt.").option("--project-id <project-id>", "The project id of the agent. Defaults to DEFAULT_PANTHEON_PROJECT_ID.").option("--task-id <task-id>", "Optional parent task id.").option("--role <role>", "Role metadata. Required for first-time project config; optional override later.").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("--concurrency <number>", "Max number of parallel running tasks for this project.", parseConcurrency).option("--max-retry-attempts <number>", "Max automatic retry attempts per task after failures.", (value) => parseNonNegativeInteger(value, "max-retry-attempts")).option("--retry-backoff-seconds <seconds>", "Base delay (seconds) for exponential retry backoff.", (value) => parsePositiveInteger(value, "retry-backoff-seconds")).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").action(async function() {
|
|
3599
|
+
return createCommand("config").version(version).description("Queue a configuration task for an agent/project").argument("<name>", "The name of the agent.").argument("[prompt]", "The configuration task prompt.").option("--project-id <project-id>", "The project id of the agent. Defaults to DEFAULT_PANTHEON_PROJECT_ID.").option("--task-id <task-id>", "Optional parent task id.").option("--role <role>", "Role metadata. Required for first-time project config; optional override later.").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("--concurrency <number>", "Max number of parallel running tasks for this project.", parseConcurrency).option("--max-retry-attempts <number>", "Max automatic retry attempts per task after failures.", (value) => parseNonNegativeInteger(value, "max-retry-attempts")).option("--retry-backoff-seconds <seconds>", "Base delay (seconds) for exponential retry backoff.", (value) => parsePositiveInteger(value, "retry-backoff-seconds")).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("--envs <json>", "Pantheon branch envs in JSON object format, e.g. '{\"ANTHROPIC_MODEL\":\"claude-opus-4-5\"}'.", parseEnvs).action(async function() {
|
|
3369
3600
|
const [name, prompt] = this.args;
|
|
3370
3601
|
const options = this.opts();
|
|
3371
3602
|
const resolvedProjectId = resolvePantheonProjectId(options.projectId);
|
|
@@ -3386,6 +3617,7 @@ function createConfigAgentCommand(version, deps = {}) {
|
|
|
3386
3617
|
maxRetryAttempts: options.maxRetryAttempts,
|
|
3387
3618
|
retryBackoffSeconds: options.retryBackoffSeconds,
|
|
3388
3619
|
skills: options.skills,
|
|
3620
|
+
envs: options.envs,
|
|
3389
3621
|
prototypeUrl: options.prototypeUrl,
|
|
3390
3622
|
rootBranchId: resolvedRootBranchId
|
|
3391
3623
|
});
|
|
@@ -3477,6 +3709,56 @@ function createGetTaskCommand(version, deps = {}) {
|
|
|
3477
3709
|
});
|
|
3478
3710
|
}
|
|
3479
3711
|
|
|
3712
|
+
//#endregion
|
|
3713
|
+
//#region src/cli/commands/kill.ts
|
|
3714
|
+
function createKillCommand(version, deps = {}) {
|
|
3715
|
+
return createCommand("kill").version(version).description("Kill a task and its descendants recursively for an agent").argument("<name>", "The name of the agent.").argument("<task-id>", "Root task id to kill.").argument("[reason]", "Optional kill reason.").option("-y, --yes", "Skip confirmation prompt.").option("-f, --force", "Alias for --yes.").action(async function() {
|
|
3716
|
+
const [name, taskId, reason] = this.args;
|
|
3717
|
+
const options = this.opts();
|
|
3718
|
+
if (!ensureEnv(["DATABASE_URL"])) return;
|
|
3719
|
+
const rl = options.yes || options.force ? null : readline.createInterface({
|
|
3720
|
+
input: process$1.stdin,
|
|
3721
|
+
output: process$1.stdout
|
|
3722
|
+
});
|
|
3723
|
+
try {
|
|
3724
|
+
if (rl) {
|
|
3725
|
+
if ((await rl.question(`Type the agent name (${name}) to confirm kill: `)).trim() !== name) {
|
|
3726
|
+
console.error("Confirmation failed. Agent name did not match.");
|
|
3727
|
+
process$1.exitCode = 1;
|
|
3728
|
+
return;
|
|
3729
|
+
}
|
|
3730
|
+
if ((await rl.question(`Type KILL ${taskId} to stop this task subtree and related branches: `)).trim() !== `KILL ${taskId}`) {
|
|
3731
|
+
console.error("Confirmation failed. Aborting kill.");
|
|
3732
|
+
process$1.exitCode = 1;
|
|
3733
|
+
return;
|
|
3734
|
+
}
|
|
3735
|
+
}
|
|
3736
|
+
const result = await (deps.killAgentTasks ?? killAgentTasks)(name, taskId, reason);
|
|
3737
|
+
if (!result) {
|
|
3738
|
+
console.error(`Task ${taskId} not found for agent ${name}.`);
|
|
3739
|
+
process$1.exitCode = 1;
|
|
3740
|
+
return;
|
|
3741
|
+
}
|
|
3742
|
+
const attemptedBranchKills = result.killed_branches.length + result.failed_branch_kills.length;
|
|
3743
|
+
if (result.pending_cancelled_count === 0 && attemptedBranchKills === 0) {
|
|
3744
|
+
console.log(`No actionable work found in task subtree ${taskId}.`);
|
|
3745
|
+
return;
|
|
3746
|
+
}
|
|
3747
|
+
console.log(`Kill result for agent ${name} task ${taskId}: cancelled ${result.pending_cancelled_count} pending task(s), cancelled ${result.running_cancelled_count} running task(s).`);
|
|
3748
|
+
if (result.killed_branches.length > 0) console.log(`Killed ${result.killed_branches.length} branch(es).`);
|
|
3749
|
+
if (result.failed_branch_kills.length > 0) {
|
|
3750
|
+
for (const failed of result.failed_branch_kills) console.error(`Failed to kill branch ${failed.branch_id} in project ${failed.project_id}: ${failed.error}`);
|
|
3751
|
+
process$1.exitCode = 1;
|
|
3752
|
+
}
|
|
3753
|
+
} catch (error) {
|
|
3754
|
+
console.error(error instanceof Error ? error.message : String(error));
|
|
3755
|
+
process$1.exitCode = 1;
|
|
3756
|
+
} finally {
|
|
3757
|
+
rl?.close();
|
|
3758
|
+
}
|
|
3759
|
+
});
|
|
3760
|
+
}
|
|
3761
|
+
|
|
3480
3762
|
//#endregion
|
|
3481
3763
|
//#region src/cli/commands/run.ts
|
|
3482
3764
|
function createRunAgentCommand(version) {
|
|
@@ -3539,7 +3821,8 @@ function createShowConfigCommand(version) {
|
|
|
3539
3821
|
role: config.role,
|
|
3540
3822
|
execute_agent: config.execute_agent,
|
|
3541
3823
|
prototype_url: config.prototype_url,
|
|
3542
|
-
skills: Array.isArray(config.skills) ? config.skills.join(", ") : String(config.skills)
|
|
3824
|
+
skills: Array.isArray(config.skills) ? config.skills.join(", ") : String(config.skills),
|
|
3825
|
+
envs: config.envs && typeof config.envs === "object" ? JSON.stringify(config.envs) : String(config.envs ?? "{}")
|
|
3543
3826
|
}]);
|
|
3544
3827
|
return;
|
|
3545
3828
|
}
|
|
@@ -3565,7 +3848,8 @@ function createShowConfigCommand(version) {
|
|
|
3565
3848
|
role: config.role,
|
|
3566
3849
|
execute_agent: config.execute_agent,
|
|
3567
3850
|
prototype_url: config.prototype_url,
|
|
3568
|
-
skills: Array.isArray(config.skills) ? config.skills.join(", ") : String(config.skills)
|
|
3851
|
+
skills: Array.isArray(config.skills) ? config.skills.join(", ") : String(config.skills),
|
|
3852
|
+
envs: config.envs && typeof config.envs === "object" ? JSON.stringify(config.envs) : String(config.envs ?? "{}")
|
|
3569
3853
|
})));
|
|
3570
3854
|
});
|
|
3571
3855
|
}
|
|
@@ -6239,7 +6523,7 @@ function createWatchStreamCommand(version) {
|
|
|
6239
6523
|
|
|
6240
6524
|
//#endregion
|
|
6241
6525
|
//#region src/cli/index.ts
|
|
6242
|
-
const program = new Command().name("pantheon-agents").description("Pantheon agents CLI").version(version).showHelpAfterError().showSuggestionAfterError().addHelpCommand().addCommand(createAddTaskCommand(version)).addCommand(createConfigAgentCommand(version)).addCommand(createDeleteTaskCommand(version)).addCommand(createCancelTaskCommand(version)).addCommand(createGetTaskCommand(version)).addCommand(createRunAgentCommand(version)).addCommand(createRetryTaskCommand(version)).addCommand(createSkillShCommand(version)).addCommand(createGenMigrationSqlCommand(version)).addCommand(createShowConfigCommand(version)).addCommand(createShowTasksCommand(version)).addCommand(createWatchCommand(version)).addCommand(createWatchStreamCommand(version));
|
|
6526
|
+
const program = new Command().name("pantheon-agents").description("Pantheon agents CLI").version(version).showHelpAfterError().showSuggestionAfterError().addHelpCommand().addCommand(createAddTaskCommand(version)).addCommand(createConfigAgentCommand(version)).addCommand(createDeleteTaskCommand(version)).addCommand(createCancelTaskCommand(version)).addCommand(createGetTaskCommand(version)).addCommand(createKillCommand(version)).addCommand(createRunAgentCommand(version)).addCommand(createRetryTaskCommand(version)).addCommand(createSkillShCommand(version)).addCommand(createGenMigrationSqlCommand(version)).addCommand(createShowConfigCommand(version)).addCommand(createShowTasksCommand(version)).addCommand(createWatchCommand(version)).addCommand(createWatchStreamCommand(version));
|
|
6243
6527
|
async function main() {
|
|
6244
6528
|
if (process$1.argv.length <= 2) {
|
|
6245
6529
|
program.outputHelp();
|