@pantheon.ai/agents 0.3.1 → 0.3.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (3) hide show
  1. package/README.md +8 -0
  2. package/dist/index.js +238 -3
  3. package/package.json +1 -1
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.0";
402
+ var version = "0.3.1";
403
403
 
404
404
  //#endregion
405
405
  //#region src/db/db9.ts
@@ -1664,7 +1664,9 @@ const projectSchema = z.object({
1664
1664
  created_at: zodJsonDate,
1665
1665
  updated_at: zodJsonDate,
1666
1666
  branches_total: z.number().int().nullable().optional(),
1667
- background_tasks_total: z.number().int().nullable().optional()
1667
+ background_tasks_total: z.number().int().nullable().optional(),
1668
+ env_id: z.string().uuid().nullable().optional(),
1669
+ env_display_name: z.string().nullable().optional()
1668
1670
  });
1669
1671
  const projectMessageSchema = z.object({
1670
1672
  id: z.string(),
@@ -2025,6 +2027,7 @@ const executor = createApiExecutor({
2025
2027
  headers: { Authorization: `Bearer ${process.env.PANTHEON_API_KEY}` },
2026
2028
  validateResponse
2027
2029
  });
2030
+ const killProjectBranchExecutionApi = defineApi(post`/api/v1/projects/${"projectId"}/branches/${"branchId"}/kill`, typeOf(), "raw");
2028
2031
  async function getPantheonProjectInfo({ projectId }) {
2029
2032
  return await executor.execute(getProject, { projectId }, null);
2030
2033
  }
@@ -2082,6 +2085,23 @@ async function executeOnPantheon({ projectId, branchId, prompt, agent }) {
2082
2085
  parent_branch_id: branchId
2083
2086
  })).branches[0];
2084
2087
  }
2088
+ function isNotFoundError(error) {
2089
+ if (!(error instanceof Error)) return false;
2090
+ const message = error.message.toLowerCase();
2091
+ return message.includes("404") || message.includes("not found");
2092
+ }
2093
+ async function killPantheonBranchExecution({ projectId, branchId }) {
2094
+ try {
2095
+ await executor.execute(killProjectBranchExecutionApi, {
2096
+ projectId,
2097
+ branchId
2098
+ }, {});
2099
+ return [branchId];
2100
+ } catch (error) {
2101
+ if (isNotFoundError(error)) return [];
2102
+ throw error;
2103
+ }
2104
+ }
2085
2105
 
2086
2106
  //#endregion
2087
2107
  //#region src/core/task-list.ts
@@ -2807,6 +2827,7 @@ var WatchStepAggregator = class {
2807
2827
  //#region src/core/index.ts
2808
2828
  const DEFAULT_MAX_RETRY_ATTEMPTS = 3;
2809
2829
  const DEFAULT_RETRY_BACKOFF_SECONDS = 30;
2830
+ const DEFAULT_KILL_REASON = "Killed by user";
2810
2831
  async function runAgent(name, options, logger) {
2811
2832
  const agentDir = path.join(options.dataDir, "agents", name);
2812
2833
  const pidFile = path.join(agentDir, "pid");
@@ -3064,6 +3085,170 @@ async function retryTask(agentName, taskId, reason) {
3064
3085
  await provider.close();
3065
3086
  }
3066
3087
  }
3088
+ function resolveTaskAttemptCount(value) {
3089
+ if (typeof value === "number" && Number.isInteger(value) && value > 0) return value;
3090
+ return 1;
3091
+ }
3092
+ function hasTaskBranchId(task) {
3093
+ return "branch_id" in task && typeof task.branch_id === "string";
3094
+ }
3095
+ function collectKillBranchTargets(tasks, options = {}) {
3096
+ const childrenByParentId = /* @__PURE__ */ new Map();
3097
+ const taskById = /* @__PURE__ */ new Map();
3098
+ for (const task of tasks) {
3099
+ taskById.set(task.id, task);
3100
+ if (!task.parent_task_id) continue;
3101
+ const siblings = childrenByParentId.get(task.parent_task_id);
3102
+ if (siblings) siblings.push(task);
3103
+ else childrenByParentId.set(task.parent_task_id, [task]);
3104
+ }
3105
+ 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");
3106
+ if (roots.length === 0) return [];
3107
+ const maxDepthByTaskId = /* @__PURE__ */ new Map();
3108
+ const stack = roots.map((task) => ({
3109
+ taskId: task.id,
3110
+ depth: 0
3111
+ }));
3112
+ while (stack.length > 0) {
3113
+ const item = stack.pop();
3114
+ if (!item) continue;
3115
+ const previousDepth = maxDepthByTaskId.get(item.taskId);
3116
+ if (previousDepth != null && previousDepth >= item.depth) continue;
3117
+ maxDepthByTaskId.set(item.taskId, item.depth);
3118
+ const children = childrenByParentId.get(item.taskId) ?? [];
3119
+ for (const child of children) stack.push({
3120
+ taskId: child.id,
3121
+ depth: item.depth + 1
3122
+ });
3123
+ }
3124
+ const scoredTargets = [];
3125
+ for (const [taskId, depth] of maxDepthByTaskId) {
3126
+ const task = taskById.get(taskId);
3127
+ if (!task || !hasTaskBranchId(task)) continue;
3128
+ scoredTargets.push({
3129
+ task_id: task.id,
3130
+ project_id: task.project_id,
3131
+ branch_id: task.branch_id,
3132
+ depth,
3133
+ queued_at_ms: task.queued_at.getTime()
3134
+ });
3135
+ }
3136
+ scoredTargets.sort((a, b) => {
3137
+ if (a.depth !== b.depth) return b.depth - a.depth;
3138
+ return b.queued_at_ms - a.queued_at_ms;
3139
+ });
3140
+ const seen = /* @__PURE__ */ new Set();
3141
+ const targets = [];
3142
+ for (const target of scoredTargets) {
3143
+ const key = `${target.project_id}:${target.branch_id}`;
3144
+ if (seen.has(key)) continue;
3145
+ seen.add(key);
3146
+ targets.push({
3147
+ task_id: target.task_id,
3148
+ project_id: target.project_id,
3149
+ branch_id: target.branch_id
3150
+ });
3151
+ }
3152
+ return targets;
3153
+ }
3154
+ function collectTaskSubtree(tasks, rootTaskId) {
3155
+ const childrenByParentId = /* @__PURE__ */ new Map();
3156
+ const taskById = /* @__PURE__ */ new Map();
3157
+ for (const task of tasks) {
3158
+ taskById.set(task.id, task);
3159
+ if (!task.parent_task_id) continue;
3160
+ const siblings = childrenByParentId.get(task.parent_task_id);
3161
+ if (siblings) siblings.push(task);
3162
+ else childrenByParentId.set(task.parent_task_id, [task]);
3163
+ }
3164
+ if (!taskById.has(rootTaskId)) return [];
3165
+ const subtreeIds = /* @__PURE__ */ new Set();
3166
+ const stack = [rootTaskId];
3167
+ while (stack.length > 0) {
3168
+ const taskId = stack.pop();
3169
+ if (!taskId || subtreeIds.has(taskId)) continue;
3170
+ subtreeIds.add(taskId);
3171
+ const children = childrenByParentId.get(taskId) ?? [];
3172
+ for (const child of children) stack.push(child.id);
3173
+ }
3174
+ return tasks.filter((task) => subtreeIds.has(task.id));
3175
+ }
3176
+ function toCancelledTask(task, reason, cancelledAt) {
3177
+ return {
3178
+ status: "cancelled",
3179
+ id: task.id,
3180
+ task: task.task,
3181
+ type: task.type,
3182
+ project_id: task.project_id,
3183
+ base_branch_id: task.base_branch_id,
3184
+ parent_task_id: task.parent_task_id ?? null,
3185
+ config_version: task.config_version ?? null,
3186
+ attempt_count: resolveTaskAttemptCount(task.attempt_count),
3187
+ cancel_reason: reason,
3188
+ queued_at: task.queued_at,
3189
+ cancelled_at: cancelledAt
3190
+ };
3191
+ }
3192
+ async function killAgentTasks(agentName, taskId, reason = DEFAULT_KILL_REASON, deps = {}) {
3193
+ const provider = createTaskListProvider(agentName, pino());
3194
+ try {
3195
+ const tasks = await provider.getTasks({
3196
+ order_by: "queued_at",
3197
+ order_direction: "asc"
3198
+ });
3199
+ const subtreeTasks = collectTaskSubtree(tasks, taskId);
3200
+ if (subtreeTasks.length === 0) return null;
3201
+ const pendingTasks = subtreeTasks.filter((task) => task.status === "pending");
3202
+ const runningTasks = subtreeTasks.filter((task) => task.status === "running");
3203
+ for (const pendingTask of pendingTasks) await provider.cancelTask(pendingTask, reason);
3204
+ const killTargets = collectKillBranchTargets(tasks, { rootTaskIds: [taskId] });
3205
+ const killBranch = deps.killBranch ?? killPantheonBranchExecution;
3206
+ const killedBranches = [];
3207
+ const seenKilledBranchKeys = /* @__PURE__ */ new Set();
3208
+ const failedBranchKills = [];
3209
+ for (const target of killTargets) try {
3210
+ const deletedBranchIds = await killBranch({
3211
+ projectId: target.project_id,
3212
+ branchId: target.branch_id
3213
+ });
3214
+ for (const deletedBranchId of deletedBranchIds) {
3215
+ const branchKey = `${target.project_id}:${deletedBranchId}`;
3216
+ if (seenKilledBranchKeys.has(branchKey)) continue;
3217
+ seenKilledBranchKeys.add(branchKey);
3218
+ killedBranches.push({
3219
+ project_id: target.project_id,
3220
+ branch_id: deletedBranchId
3221
+ });
3222
+ }
3223
+ } catch (error) {
3224
+ const message = error instanceof Error ? error.message : String(error);
3225
+ failedBranchKills.push({
3226
+ task_id: target.task_id,
3227
+ project_id: target.project_id,
3228
+ branch_id: target.branch_id,
3229
+ error: message
3230
+ });
3231
+ }
3232
+ const now = /* @__PURE__ */ new Date();
3233
+ const killedBranchKeys = new Set(killedBranches.map((item) => `${item.project_id}:${item.branch_id}`));
3234
+ let runningCancelledCount = 0;
3235
+ for (const runningTask of runningTasks) {
3236
+ const branchKey = `${runningTask.project_id}:${runningTask.branch_id}`;
3237
+ if (!killedBranchKeys.has(branchKey)) continue;
3238
+ await provider.updateTask(toCancelledTask(runningTask, reason, now));
3239
+ runningCancelledCount++;
3240
+ }
3241
+ return {
3242
+ task_id: taskId,
3243
+ pending_cancelled_count: pendingTasks.length,
3244
+ running_cancelled_count: runningCancelledCount,
3245
+ killed_branches: killedBranches,
3246
+ failed_branch_kills: failedBranchKills
3247
+ };
3248
+ } finally {
3249
+ await provider.close();
3250
+ }
3251
+ }
3067
3252
  async function showAgentConfig(agentName, projectId) {
3068
3253
  const provider = createTaskListProvider(agentName, pino());
3069
3254
  try {
@@ -3477,6 +3662,56 @@ function createGetTaskCommand(version, deps = {}) {
3477
3662
  });
3478
3663
  }
3479
3664
 
3665
+ //#endregion
3666
+ //#region src/cli/commands/kill.ts
3667
+ function createKillCommand(version, deps = {}) {
3668
+ 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() {
3669
+ const [name, taskId, reason] = this.args;
3670
+ const options = this.opts();
3671
+ if (!ensureEnv(["DATABASE_URL"])) return;
3672
+ const rl = options.yes || options.force ? null : readline.createInterface({
3673
+ input: process$1.stdin,
3674
+ output: process$1.stdout
3675
+ });
3676
+ try {
3677
+ if (rl) {
3678
+ if ((await rl.question(`Type the agent name (${name}) to confirm kill: `)).trim() !== name) {
3679
+ console.error("Confirmation failed. Agent name did not match.");
3680
+ process$1.exitCode = 1;
3681
+ return;
3682
+ }
3683
+ if ((await rl.question(`Type KILL ${taskId} to stop this task subtree and related branches: `)).trim() !== `KILL ${taskId}`) {
3684
+ console.error("Confirmation failed. Aborting kill.");
3685
+ process$1.exitCode = 1;
3686
+ return;
3687
+ }
3688
+ }
3689
+ const result = await (deps.killAgentTasks ?? killAgentTasks)(name, taskId, reason);
3690
+ if (!result) {
3691
+ console.error(`Task ${taskId} not found for agent ${name}.`);
3692
+ process$1.exitCode = 1;
3693
+ return;
3694
+ }
3695
+ const attemptedBranchKills = result.killed_branches.length + result.failed_branch_kills.length;
3696
+ if (result.pending_cancelled_count === 0 && attemptedBranchKills === 0) {
3697
+ console.log(`No actionable work found in task subtree ${taskId}.`);
3698
+ return;
3699
+ }
3700
+ 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).`);
3701
+ if (result.killed_branches.length > 0) console.log(`Killed ${result.killed_branches.length} branch(es).`);
3702
+ if (result.failed_branch_kills.length > 0) {
3703
+ for (const failed of result.failed_branch_kills) console.error(`Failed to kill branch ${failed.branch_id} in project ${failed.project_id}: ${failed.error}`);
3704
+ process$1.exitCode = 1;
3705
+ }
3706
+ } catch (error) {
3707
+ console.error(error instanceof Error ? error.message : String(error));
3708
+ process$1.exitCode = 1;
3709
+ } finally {
3710
+ rl?.close();
3711
+ }
3712
+ });
3713
+ }
3714
+
3480
3715
  //#endregion
3481
3716
  //#region src/cli/commands/run.ts
3482
3717
  function createRunAgentCommand(version) {
@@ -6239,7 +6474,7 @@ function createWatchStreamCommand(version) {
6239
6474
 
6240
6475
  //#endregion
6241
6476
  //#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));
6477
+ 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
6478
  async function main() {
6244
6479
  if (process$1.argv.length <= 2) {
6245
6480
  program.outputHelp();
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@pantheon.ai/agents",
3
3
  "type": "module",
4
- "version": "0.3.1",
4
+ "version": "0.3.2",
5
5
  "bin": {
6
6
  "pantheon-agents": "dist/index.js"
7
7
  },