opencode-supertask 0.1.26 → 0.1.27

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.
@@ -9100,8 +9100,8 @@ var require_CronFileParser = __commonJS({
9100
9100
  * @throws If file cannot be read
9101
9101
  */
9102
9102
  static parseFileSync(filePath) {
9103
- const { readFileSync: readFileSync4 } = __require("fs");
9104
- const data = readFileSync4(filePath, "utf8");
9103
+ const { readFileSync: readFileSync5 } = __require("fs");
9104
+ const data = readFileSync5(filePath, "utf8");
9105
9105
  return _CronFileParser.#parseContent(data);
9106
9106
  }
9107
9107
  /**
@@ -22653,7 +22653,7 @@ function sql(strings, ...params) {
22653
22653
  return new SQL([new StringChunk(str)]);
22654
22654
  }
22655
22655
  sql2.raw = raw;
22656
- function join5(chunks, separator) {
22656
+ function join6(chunks, separator) {
22657
22657
  const result = [];
22658
22658
  for (const [i, chunk] of chunks.entries()) {
22659
22659
  if (i > 0 && separator !== void 0) {
@@ -22663,7 +22663,7 @@ function sql(strings, ...params) {
22663
22663
  }
22664
22664
  return new SQL(result);
22665
22665
  }
22666
- sql2.join = join5;
22666
+ sql2.join = join6;
22667
22667
  function identifier(value) {
22668
22668
  return new Name(value);
22669
22669
  }
@@ -24959,7 +24959,7 @@ var SQLiteSelectQueryBuilderBase = class extends TypedQueryBuilder {
24959
24959
  return (table, on) => {
24960
24960
  const baseTableName = this.tableName;
24961
24961
  const tableName = getTableLikeName(table);
24962
- if (typeof tableName === "string" && this.config.joins?.some((join5) => join5.alias === tableName)) {
24962
+ if (typeof tableName === "string" && this.config.joins?.some((join6) => join6.alias === tableName)) {
24963
24963
  throw new Error(`Alias "${tableName}" is already used in this query`);
24964
24964
  }
24965
24965
  if (!this.isPartialSelect) {
@@ -25772,7 +25772,7 @@ var SQLiteUpdateBase = class extends QueryPromise {
25772
25772
  createJoin(joinType) {
25773
25773
  return (table, on) => {
25774
25774
  const tableName = getTableLikeName(table);
25775
- if (typeof tableName === "string" && this.config.joins.some((join5) => join5.alias === tableName)) {
25775
+ if (typeof tableName === "string" && this.config.joins.some((join6) => join6.alias === tableName)) {
25776
25776
  throw new Error(`Alias "${tableName}" is already used in this query`);
25777
25777
  }
25778
25778
  if (typeof on === "function") {
@@ -26802,7 +26802,9 @@ var tasks = sqliteTable("tasks", {
26802
26802
  }, (table) => [
26803
26803
  index("tasks_queue_idx").on(table.status, table.retryAfter, table.urgency, table.importance, table.createdAt, table.id),
26804
26804
  index("tasks_batch_status_idx").on(table.batchId, table.status),
26805
- index("tasks_template_status_idx").on(table.templateId, table.status)
26805
+ index("tasks_template_status_idx").on(table.templateId, table.status),
26806
+ index("tasks_depends_on_status_idx").on(table.dependsOn, table.status),
26807
+ index("tasks_cleanup_idx").on(table.finishedAt, table.id, table.status)
26806
26808
  ]);
26807
26809
  var TASK_CATEGORIES = [
26808
26810
  "translate",
@@ -26825,7 +26827,8 @@ var taskRuns = sqliteTable("task_runs", {
26825
26827
  lockedBy: text("locked_by"),
26826
26828
  heartbeatAt: integer2("heartbeat_at"),
26827
26829
  workerPid: integer2("worker_pid"),
26828
- childPid: integer2("child_pid")
26830
+ childPid: integer2("child_pid"),
26831
+ launchProtocol: text("launch_protocol")
26829
26832
  }, (table) => [
26830
26833
  index("task_runs_task_started_idx").on(table.taskId, table.startedAt, table.id),
26831
26834
  index("task_runs_status_heartbeat_idx").on(table.status, table.heartbeatAt)
@@ -26855,7 +26858,13 @@ var taskTemplates = sqliteTable("task_templates", {
26855
26858
  createdAt: integer2("created_at").default(0),
26856
26859
  updatedAt: integer2("updated_at").default(0)
26857
26860
  }, (table) => [
26858
- index("task_templates_due_idx").on(table.enabled, table.nextRunAt, table.id)
26861
+ index("task_templates_due_idx").on(table.enabled, table.nextRunAt, table.id),
26862
+ index("task_templates_retention_idx").on(
26863
+ table.scheduleType,
26864
+ table.enabled,
26865
+ table.lastRunAt,
26866
+ table.id
26867
+ )
26859
26868
  ]);
26860
26869
 
26861
26870
  // src/core/db/index.ts
@@ -26880,36 +26889,47 @@ function getMigrationsFolder() {
26880
26889
  }
26881
26890
  return join(__dirname2, "../../drizzle");
26882
26891
  }
26883
- function initDb() {
26884
- const dataDir = dirname(DB_FILE_PATH);
26885
- if (!existsSync(dataDir)) {
26886
- mkdirSync(dataDir, { recursive: true });
26887
- }
26888
- _sqlite = new Database2(DB_FILE_PATH);
26889
- _sqlite.exec("PRAGMA journal_mode = WAL;");
26890
- _sqlite.exec("PRAGMA busy_timeout = 5000;");
26891
- _sqlite.exec(`
26892
+ function ensureGatewayLock(sqliteDb) {
26893
+ sqliteDb.exec(`
26892
26894
  CREATE TABLE IF NOT EXISTS gateway_lock (
26893
26895
  id INTEGER PRIMARY KEY CHECK (id = 1),
26894
26896
  pid INTEGER NOT NULL,
26895
26897
  acquired_at INTEGER NOT NULL,
26896
26898
  heartbeat_at INTEGER NOT NULL,
26897
- ready_at INTEGER
26899
+ ready_at INTEGER,
26900
+ version TEXT
26898
26901
  );
26899
26902
  `);
26900
- const gatewayLockColumns = _sqlite.query("PRAGMA table_info(gateway_lock)").all();
26901
- if (!gatewayLockColumns.some((column) => column.name === "ready_at")) {
26902
- _sqlite.exec("ALTER TABLE gateway_lock ADD COLUMN ready_at INTEGER;");
26903
+ const columns = sqliteDb.query("PRAGMA table_info(gateway_lock)").all();
26904
+ if (!columns.some((column) => column.name === "ready_at")) {
26905
+ sqliteDb.exec("ALTER TABLE gateway_lock ADD COLUMN ready_at INTEGER;");
26906
+ }
26907
+ if (!columns.some((column) => column.name === "version")) {
26908
+ sqliteDb.exec("ALTER TABLE gateway_lock ADD COLUMN version TEXT;");
26903
26909
  }
26904
- _db = drizzle(_sqlite, { schema: schema_exports });
26910
+ }
26911
+ function migrateSqliteDatabase(sqliteDb) {
26912
+ ensureGatewayLock(sqliteDb);
26913
+ const drizzleDb = drizzle(sqliteDb, { schema: schema_exports });
26914
+ migrate(drizzleDb, { migrationsFolder: getMigrationsFolder() });
26915
+ sqliteDb.exec("PRAGMA foreign_keys = ON;");
26916
+ const violations = sqliteDb.query("PRAGMA foreign_key_check;").all();
26917
+ if (violations.length > 0) {
26918
+ throw new Error(`\u68C0\u6D4B\u5230 ${violations.length} \u6761\u5B64\u7ACB\u5173\u8054\u8BB0\u5F55\uFF0C\u8BF7\u5148\u4FEE\u590D\u6570\u636E\u518D\u542F\u52A8`);
26919
+ }
26920
+ return drizzleDb;
26921
+ }
26922
+ function initDb() {
26923
+ const dataDir = dirname(DB_FILE_PATH);
26924
+ if (!existsSync(dataDir)) {
26925
+ mkdirSync(dataDir, { recursive: true });
26926
+ }
26927
+ _sqlite = new Database2(DB_FILE_PATH);
26928
+ _sqlite.exec("PRAGMA journal_mode = WAL;");
26929
+ _sqlite.exec("PRAGMA busy_timeout = 5000;");
26905
26930
  if (!_migrationRan) {
26906
26931
  try {
26907
- migrate(_db, { migrationsFolder: getMigrationsFolder() });
26908
- _sqlite.exec("PRAGMA foreign_keys = ON;");
26909
- const violations = _sqlite.query("PRAGMA foreign_key_check;").all();
26910
- if (violations.length > 0) {
26911
- throw new Error(`\u68C0\u6D4B\u5230 ${violations.length} \u6761\u5B64\u7ACB\u5173\u8054\u8BB0\u5F55\uFF0C\u8BF7\u5148\u4FEE\u590D\u6570\u636E\u518D\u542F\u52A8`);
26912
- }
26932
+ _db = migrateSqliteDatabase(_sqlite);
26913
26933
  _migrationRan = true;
26914
26934
  } catch (err) {
26915
26935
  const msg = err instanceof Error ? err.message : String(err);
@@ -26920,6 +26940,8 @@ function initDb() {
26920
26940
  console.error(`[supertask] migration failed: ${msg}`);
26921
26941
  throw new Error(`[supertask] DB migration failed: ${msg}`);
26922
26942
  }
26943
+ } else {
26944
+ _db = drizzle(_sqlite, { schema: schema_exports });
26923
26945
  }
26924
26946
  return _db;
26925
26947
  }
@@ -26956,6 +26978,58 @@ function computeBackoff(retryCount, baseMs = 3e4, maxMs = MAX_BACKOFF_MS) {
26956
26978
 
26957
26979
  // src/core/services/task.service.ts
26958
26980
  var { tasks: tasks2, taskRuns: taskRuns2 } = schema_exports;
26981
+ var cleanupInvocation = 0;
26982
+ var TaskDeletionConflictError = class extends Error {
26983
+ constructor(message) {
26984
+ super(message);
26985
+ this.name = "TaskDeletionConflictError";
26986
+ }
26987
+ };
26988
+ function hasNoExecutableDependents() {
26989
+ return sql`NOT EXISTS (
26990
+ SELECT 1 FROM tasks AS dependent_task
26991
+ WHERE dependent_task.depends_on = ${tasks2.id}
26992
+ AND dependent_task.status IN ('pending', 'running', 'failed', 'dead_letter')
26993
+ )`;
26994
+ }
26995
+ function hasViableDependency() {
26996
+ return or(
26997
+ isNull(tasks2.dependsOn),
26998
+ sql`EXISTS (
26999
+ SELECT 1 FROM tasks AS dependency_task
27000
+ WHERE dependency_task.id = ${tasks2.dependsOn}
27001
+ AND dependency_task.cwd IS ${tasks2.cwd}
27002
+ AND (
27003
+ dependency_task.status IN ('pending', 'running', 'done')
27004
+ OR (
27005
+ dependency_task.status = 'failed'
27006
+ AND dependency_task.retry_count <= dependency_task.max_retries
27007
+ )
27008
+ )
27009
+ )`
27010
+ );
27011
+ }
27012
+ function blockedDependentsOf(prerequisiteId) {
27013
+ return sql`${tasks2.id} IN (
27014
+ WITH RECURSIVE blocked_task(id) AS (
27015
+ SELECT direct_dependent.id
27016
+ FROM tasks AS direct_dependent
27017
+ WHERE direct_dependent.depends_on = ${prerequisiteId}
27018
+ AND direct_dependent.status IN ('pending', 'failed')
27019
+ AND EXISTS (
27020
+ SELECT 1 FROM tasks AS prerequisite
27021
+ WHERE prerequisite.id = ${prerequisiteId}
27022
+ AND prerequisite.status IN ('dead_letter', 'cancelled')
27023
+ )
27024
+ UNION
27025
+ SELECT descendant.id
27026
+ FROM tasks AS descendant
27027
+ INNER JOIN blocked_task ON descendant.depends_on = blocked_task.id
27028
+ WHERE descendant.status IN ('pending', 'failed')
27029
+ )
27030
+ SELECT id FROM blocked_task
27031
+ )`;
27032
+ }
26959
27033
  var TaskService = class {
26960
27034
  static buildScopeWhere(scope) {
26961
27035
  const conditions = [];
@@ -26966,8 +27040,28 @@ var TaskService = class {
26966
27040
  }
26967
27041
  static async add(data) {
26968
27042
  this.validateNewTask(data);
26969
- const result = await db.insert(tasks2).values(data).returning();
26970
- return result[0];
27043
+ return db.transaction((tx) => {
27044
+ if (data.dependsOn != null) {
27045
+ const dependency = tx.select({
27046
+ id: tasks2.id,
27047
+ cwd: tasks2.cwd,
27048
+ status: tasks2.status,
27049
+ retryCount: tasks2.retryCount,
27050
+ maxRetries: tasks2.maxRetries
27051
+ }).from(tasks2).where(eq(tasks2.id, data.dependsOn)).get();
27052
+ if (!dependency) {
27053
+ throw new Error(`dependsOn \u6307\u5411\u7684\u4EFB\u52A1 #${data.dependsOn} \u4E0D\u5B58\u5728`);
27054
+ }
27055
+ if ((dependency.cwd ?? null) !== (data.cwd ?? null)) {
27056
+ throw new Error("dependsOn \u5FC5\u987B\u6307\u5411\u540C\u4E00 cwd \u7684\u4EFB\u52A1");
27057
+ }
27058
+ const dependencyIsRecoverable = dependency.status === "pending" || dependency.status === "running" || dependency.status === "done" || dependency.status === "failed" && (dependency.retryCount ?? 0) <= (dependency.maxRetries ?? 3);
27059
+ if (!dependencyIsRecoverable) {
27060
+ throw new Error(`dependsOn \u6307\u5411\u7684\u4EFB\u52A1 #${data.dependsOn} \u5DF2\u8FDB\u5165\u4E0D\u53EF\u6062\u590D\u7EC8\u6001`);
27061
+ }
27062
+ }
27063
+ return tx.insert(tasks2).values(data).returning().get();
27064
+ }, { behavior: "immediate" });
26971
27065
  }
26972
27066
  static validateNewTask(data) {
26973
27067
  if (!data.name.trim()) throw new Error("name \u4E0D\u80FD\u4E3A\u7A7A");
@@ -27019,23 +27113,54 @@ var TaskService = class {
27019
27113
  if (batchFilter) {
27020
27114
  conditions.push(batchFilter);
27021
27115
  }
27022
- const allTasks = await db.select().from(tasks2).where(and(...conditions)).orderBy(
27116
+ const result = await db.select().from(tasks2).where(and(
27117
+ ...conditions,
27118
+ or(
27119
+ isNull(tasks2.dependsOn),
27120
+ sql`EXISTS (
27121
+ SELECT 1 FROM tasks AS dependency_task
27122
+ WHERE dependency_task.id = ${tasks2.dependsOn}
27123
+ AND dependency_task.status = 'done'
27124
+ AND dependency_task.cwd IS ${tasks2.cwd}
27125
+ )`
27126
+ ),
27127
+ or(
27128
+ isNull(tasks2.batchId),
27129
+ sql`NOT EXISTS (
27130
+ SELECT 1 FROM tasks AS running_batch_task
27131
+ WHERE running_batch_task.batch_id = ${tasks2.batchId}
27132
+ AND (
27133
+ running_batch_task.status = 'running'
27134
+ OR EXISTS (
27135
+ SELECT 1 FROM task_runs AS running_batch_run
27136
+ WHERE running_batch_run.task_id = running_batch_task.id
27137
+ AND running_batch_run.status = 'running'
27138
+ )
27139
+ )
27140
+ )`
27141
+ ),
27142
+ sql`NOT EXISTS (
27143
+ SELECT 1 FROM task_runs AS candidate_active_run
27144
+ WHERE candidate_active_run.task_id = ${tasks2.id}
27145
+ AND candidate_active_run.status = 'running'
27146
+ )`
27147
+ )).orderBy(
27023
27148
  desc(tasks2.urgency),
27024
27149
  desc(tasks2.importance),
27025
27150
  asc(tasks2.createdAt),
27026
27151
  asc(tasks2.id)
27027
- );
27028
- for (const task of allTasks) {
27029
- if (task.dependsOn) {
27030
- const depTask = await this.getById(task.dependsOn, scope);
27031
- if (depTask && depTask.status === "done") {
27032
- return task;
27033
- }
27034
- continue;
27035
- }
27036
- return task;
27037
- }
27038
- return null;
27152
+ ).limit(1);
27153
+ return result[0] ?? null;
27154
+ }
27155
+ static async countRunning() {
27156
+ return db.transaction((tx) => {
27157
+ const runningTasks = tx.select({ count: sql`count(*)` }).from(tasks2).where(eq(tasks2.status, "running")).get();
27158
+ const runsWithoutRunningTask = tx.select({ count: sql`count(DISTINCT ${taskRuns2.taskId})` }).from(taskRuns2).innerJoin(tasks2, eq(tasks2.id, taskRuns2.taskId)).where(and(
27159
+ eq(taskRuns2.status, "running"),
27160
+ sql`${tasks2.status} <> 'running'`
27161
+ )).get();
27162
+ return Number(runningTasks?.count ?? 0) + Number(runsWithoutRunningTask?.count ?? 0);
27163
+ }, { behavior: "deferred" });
27039
27164
  }
27040
27165
  static async start(id, scope = {}) {
27041
27166
  const conditions = [
@@ -27071,27 +27196,202 @@ var TaskService = class {
27071
27196
  return result[0] || null;
27072
27197
  }
27073
27198
  static async fail(id, log, scope = {}, options) {
27074
- const current = await this.getById(id, scope);
27075
- if (!current || current.status !== "running") return null;
27076
- const newRetryCount = (current.retryCount ?? 0) + 1;
27077
- const maxRetries = current.maxRetries ?? 3;
27078
- const isDeadLetter = options?.setDeadLetter ?? newRetryCount > maxRetries;
27079
- const conditions = [
27080
- eq(tasks2.id, id),
27081
- eq(tasks2.status, "running"),
27082
- ...this.buildScopeWhere(scope)
27083
- ];
27199
+ return db.transaction((tx) => {
27200
+ const current = tx.select().from(tasks2).where(and(
27201
+ eq(tasks2.id, id),
27202
+ eq(tasks2.status, "running"),
27203
+ ...this.buildScopeWhere(scope)
27204
+ )).get();
27205
+ if (!current) return null;
27206
+ const newRetryCount = (current.retryCount ?? 0) + 1;
27207
+ const maxRetries = current.maxRetries ?? 3;
27208
+ const isDeadLetter = options?.setDeadLetter ?? newRetryCount > maxRetries;
27209
+ const failed = tx.update(tasks2).set({
27210
+ status: isDeadLetter ? "dead_letter" : "failed",
27211
+ finishedAt: /* @__PURE__ */ new Date(),
27212
+ resultLog: log,
27213
+ retryCount: newRetryCount,
27214
+ retryAfter: isDeadLetter ? null : options?.retryAfterMs ?? Date.now() + computeBackoff(
27215
+ newRetryCount,
27216
+ current.retryBackoffMs ?? 3e4
27217
+ )
27218
+ }).where(and(eq(tasks2.id, id), eq(tasks2.status, "running"))).returning().get();
27219
+ if (failed?.status === "dead_letter") {
27220
+ tx.update(tasks2).set({
27221
+ status: "dead_letter",
27222
+ finishedAt: /* @__PURE__ */ new Date(),
27223
+ retryAfter: null,
27224
+ resultLog: `\u4F9D\u8D56\u4EFB\u52A1 #${id} \u5DF2\u8FDB\u5165\u4E0D\u53EF\u6062\u590D\u7EC8\u6001`
27225
+ }).where(blockedDependentsOf(id)).run();
27226
+ }
27227
+ return failed ?? null;
27228
+ }, { behavior: "immediate" });
27229
+ }
27230
+ static async completeRun(taskId, runId, log) {
27231
+ return db.transaction((tx) => {
27232
+ const currentTask = tx.select().from(tasks2).where(and(eq(tasks2.id, taskId), eq(tasks2.status, "running"))).get();
27233
+ const currentRun = tx.select({ id: taskRuns2.id }).from(taskRuns2).where(and(
27234
+ eq(taskRuns2.id, runId),
27235
+ eq(taskRuns2.taskId, taskId),
27236
+ eq(taskRuns2.status, "running")
27237
+ )).get();
27238
+ if (!currentTask || !currentRun) return null;
27239
+ const finishedAt = /* @__PURE__ */ new Date();
27240
+ const completed = tx.update(tasks2).set({
27241
+ status: "done",
27242
+ finishedAt,
27243
+ resultLog: log,
27244
+ retryAfter: null
27245
+ }).where(and(eq(tasks2.id, taskId), eq(tasks2.status, "running"))).returning().get();
27246
+ if (!completed) return null;
27247
+ tx.update(taskRuns2).set({ status: "done", finishedAt, log }).where(and(eq(taskRuns2.id, runId), eq(taskRuns2.status, "running"))).run();
27248
+ return completed;
27249
+ }, { behavior: "immediate" });
27250
+ }
27251
+ static async failRun(taskId, runId, log, options) {
27252
+ const failed = db.transaction((tx) => {
27253
+ const currentTask = tx.select().from(tasks2).where(and(eq(tasks2.id, taskId), eq(tasks2.status, "running"))).get();
27254
+ const currentRun = tx.select({ id: taskRuns2.id }).from(taskRuns2).where(and(
27255
+ eq(taskRuns2.id, runId),
27256
+ eq(taskRuns2.taskId, taskId),
27257
+ eq(taskRuns2.status, "running")
27258
+ )).get();
27259
+ if (!currentTask || !currentRun) return null;
27260
+ const newRetryCount = (currentTask.retryCount ?? 0) + 1;
27261
+ const maxRetries = currentTask.maxRetries ?? 3;
27262
+ const isDeadLetter = options?.setDeadLetter ?? newRetryCount > maxRetries;
27263
+ const finishedAt = /* @__PURE__ */ new Date();
27264
+ const retryAfter = isDeadLetter ? null : options?.retryAfterMs ?? Date.now() + computeBackoff(
27265
+ newRetryCount,
27266
+ currentTask.retryBackoffMs ?? 3e4
27267
+ );
27268
+ const failed2 = tx.update(tasks2).set({
27269
+ status: isDeadLetter ? "dead_letter" : "failed",
27270
+ finishedAt,
27271
+ resultLog: log,
27272
+ retryCount: newRetryCount,
27273
+ retryAfter
27274
+ }).where(and(eq(tasks2.id, taskId), eq(tasks2.status, "running"))).returning().get();
27275
+ if (!failed2) return null;
27276
+ tx.update(taskRuns2).set({ status: "failed", finishedAt, log }).where(and(eq(taskRuns2.id, runId), eq(taskRuns2.status, "running"))).run();
27277
+ if (failed2.status === "dead_letter") {
27278
+ tx.update(tasks2).set({
27279
+ status: "dead_letter",
27280
+ finishedAt,
27281
+ retryAfter: null,
27282
+ resultLog: `\u4F9D\u8D56\u4EFB\u52A1 #${taskId} \u5DF2\u8FDB\u5165\u4E0D\u53EF\u6062\u590D\u7EC8\u6001`
27283
+ }).where(blockedDependentsOf(taskId)).run();
27284
+ }
27285
+ return failed2;
27286
+ }, { behavior: "immediate" });
27287
+ return failed;
27288
+ }
27289
+ static async recoverRun(taskId, runId, log) {
27290
+ const recovery = db.transaction((tx) => {
27291
+ const currentTask = tx.select().from(tasks2).where(and(eq(tasks2.id, taskId), eq(tasks2.status, "running"))).get();
27292
+ const currentRun = tx.select({ id: taskRuns2.id }).from(taskRuns2).where(and(
27293
+ eq(taskRuns2.id, runId),
27294
+ eq(taskRuns2.taskId, taskId),
27295
+ eq(taskRuns2.status, "running")
27296
+ )).get();
27297
+ if (!currentRun) return null;
27298
+ const finishedAt = /* @__PURE__ */ new Date();
27299
+ tx.update(taskRuns2).set({ status: "failed", finishedAt, log }).where(and(eq(taskRuns2.id, runId), eq(taskRuns2.status, "running"))).run();
27300
+ if (!currentTask) return null;
27301
+ const retryCount = (currentTask.retryCount ?? 0) + 1;
27302
+ const maxRetries = currentTask.maxRetries ?? 3;
27303
+ const isDeadLetter = retryCount > maxRetries;
27304
+ const recoveryStatus = isDeadLetter ? "dead_letter" : "pending";
27305
+ const retryAfterMs = isDeadLetter ? null : Date.now() + computeBackoff(
27306
+ retryCount,
27307
+ currentTask.retryBackoffMs ?? 3e4
27308
+ );
27309
+ tx.update(tasks2).set({
27310
+ status: recoveryStatus,
27311
+ startedAt: null,
27312
+ finishedAt: isDeadLetter ? finishedAt : null,
27313
+ retryCount,
27314
+ retryAfter: retryAfterMs,
27315
+ resultLog: log
27316
+ }).where(and(eq(tasks2.id, taskId), eq(tasks2.status, "running"))).run();
27317
+ if (recoveryStatus === "dead_letter") {
27318
+ tx.update(tasks2).set({
27319
+ status: "dead_letter",
27320
+ finishedAt,
27321
+ retryAfter: null,
27322
+ resultLog: `\u4F9D\u8D56\u4EFB\u52A1 #${taskId} \u5DF2\u8FDB\u5165\u4E0D\u53EF\u6062\u590D\u7EC8\u6001`
27323
+ }).where(blockedDependentsOf(taskId)).run();
27324
+ }
27325
+ return {
27326
+ status: recoveryStatus,
27327
+ retryCount,
27328
+ retryAfterMs
27329
+ };
27330
+ }, { behavior: "immediate" });
27331
+ return recovery;
27332
+ }
27333
+ static async interruptRun(taskId, runId, log) {
27334
+ return db.transaction((tx) => {
27335
+ const currentRun = tx.select({ id: taskRuns2.id }).from(taskRuns2).where(and(
27336
+ eq(taskRuns2.id, runId),
27337
+ eq(taskRuns2.taskId, taskId),
27338
+ eq(taskRuns2.status, "running")
27339
+ )).get();
27340
+ if (!currentRun) return false;
27341
+ const updatedRun = tx.update(taskRuns2).set({ status: "failed", finishedAt: /* @__PURE__ */ new Date(), log }).where(and(eq(taskRuns2.id, runId), eq(taskRuns2.status, "running"))).returning({ id: taskRuns2.id }).get();
27342
+ tx.update(tasks2).set({
27343
+ status: "pending",
27344
+ startedAt: null,
27345
+ finishedAt: null,
27346
+ retryAfter: null,
27347
+ resultLog: log
27348
+ }).where(and(eq(tasks2.id, taskId), eq(tasks2.status, "running"))).run();
27349
+ return updatedRun != null;
27350
+ }, { behavior: "immediate" });
27351
+ }
27352
+ static async resolveBlockedDependencies() {
27353
+ const finishedAt = /* @__PURE__ */ new Date();
27084
27354
  const result = await db.update(tasks2).set({
27085
- status: isDeadLetter ? "dead_letter" : "failed",
27355
+ status: "dead_letter",
27356
+ finishedAt,
27357
+ retryAfter: null,
27358
+ resultLog: "\u4F9D\u8D56\u4EFB\u52A1\u4E0D\u5B58\u5728\u3001\u8DE8\u9879\u76EE\u6216\u5DF2\u8FDB\u5165\u4E0D\u53EF\u6062\u590D\u7EC8\u6001"
27359
+ }).where(sql`${tasks2.id} IN (
27360
+ WITH RECURSIVE blocked_task(id) AS (
27361
+ SELECT candidate.id
27362
+ FROM tasks AS candidate
27363
+ WHERE candidate.status IN ('pending', 'failed')
27364
+ AND candidate.depends_on IS NOT NULL
27365
+ AND NOT EXISTS (
27366
+ SELECT 1 FROM tasks AS viable_dependency
27367
+ WHERE viable_dependency.id = candidate.depends_on
27368
+ AND viable_dependency.cwd IS candidate.cwd
27369
+ AND (
27370
+ viable_dependency.status IN ('pending', 'running', 'done')
27371
+ OR (
27372
+ viable_dependency.status = 'failed'
27373
+ AND viable_dependency.retry_count <= viable_dependency.max_retries
27374
+ )
27375
+ )
27376
+ )
27377
+ UNION
27378
+ SELECT descendant.id
27379
+ FROM tasks AS descendant
27380
+ INNER JOIN blocked_task ON descendant.depends_on = blocked_task.id
27381
+ WHERE descendant.status IN ('pending', 'failed')
27382
+ )
27383
+ SELECT id FROM blocked_task
27384
+ )`).returning();
27385
+ return result.length;
27386
+ }
27387
+ static async rejectBlockedDependents(prerequisiteId) {
27388
+ const result = await db.update(tasks2).set({
27389
+ status: "dead_letter",
27086
27390
  finishedAt: /* @__PURE__ */ new Date(),
27087
- resultLog: log,
27088
- retryCount: newRetryCount,
27089
- retryAfter: isDeadLetter ? null : options?.retryAfterMs ?? Date.now() + computeBackoff(
27090
- newRetryCount,
27091
- current.retryBackoffMs ?? 3e4
27092
- )
27093
- }).where(and(...conditions)).returning();
27094
- return result[0] || null;
27391
+ retryAfter: null,
27392
+ resultLog: `\u4F9D\u8D56\u4EFB\u52A1 #${prerequisiteId} \u5DF2\u8FDB\u5165\u4E0D\u53EF\u6062\u590D\u7EC8\u6001`
27393
+ }).where(blockedDependentsOf(prerequisiteId)).returning({ id: tasks2.id });
27394
+ return result.length;
27095
27395
  }
27096
27396
  static async markPendingForRetry(id, retryAfterMs, retryCount) {
27097
27397
  const result = await db.update(tasks2).set({
@@ -27104,8 +27404,18 @@ var TaskService = class {
27104
27404
  return result[0] || null;
27105
27405
  }
27106
27406
  static async markDeadLetter(id, retryCount) {
27107
- const result = await db.update(tasks2).set({ status: "dead_letter", finishedAt: /* @__PURE__ */ new Date(), retryCount }).where(eq(tasks2.id, id)).returning();
27108
- return result[0] || null;
27407
+ return db.transaction((tx) => {
27408
+ const finishedAt = /* @__PURE__ */ new Date();
27409
+ const task = tx.update(tasks2).set({ status: "dead_letter", finishedAt, retryCount }).where(eq(tasks2.id, id)).returning().get();
27410
+ if (!task) return null;
27411
+ tx.update(tasks2).set({
27412
+ status: "dead_letter",
27413
+ finishedAt,
27414
+ retryAfter: null,
27415
+ resultLog: `\u4F9D\u8D56\u4EFB\u52A1 #${id} \u5DF2\u8FDB\u5165\u4E0D\u53EF\u6062\u590D\u7EC8\u6001`
27416
+ }).where(blockedDependentsOf(id)).run();
27417
+ return task;
27418
+ }, { behavior: "immediate" });
27109
27419
  }
27110
27420
  static async resetRunningToPending(ids) {
27111
27421
  if (ids.length === 0) return 0;
@@ -27148,41 +27458,91 @@ var TaskService = class {
27148
27458
  ),
27149
27459
  ...this.buildScopeWhere(scope)
27150
27460
  ];
27151
- const result = await db.update(tasks2).set({
27152
- status: "cancelled",
27153
- finishedAt: /* @__PURE__ */ new Date(),
27154
- retryAfter: null
27155
- }).where(and(...conditions)).returning();
27156
- return result[0] || null;
27461
+ return db.transaction((tx) => {
27462
+ const finishedAt = /* @__PURE__ */ new Date();
27463
+ const task = tx.update(tasks2).set({
27464
+ status: "cancelled",
27465
+ finishedAt,
27466
+ retryAfter: null
27467
+ }).where(and(...conditions)).returning().get();
27468
+ if (!task) return null;
27469
+ tx.update(tasks2).set({
27470
+ status: "dead_letter",
27471
+ finishedAt,
27472
+ retryAfter: null,
27473
+ resultLog: `\u4F9D\u8D56\u4EFB\u52A1 #${id} \u5DF2\u8FDB\u5165\u4E0D\u53EF\u6062\u590D\u7EC8\u6001`
27474
+ }).where(blockedDependentsOf(id)).run();
27475
+ return task;
27476
+ }, { behavior: "immediate" });
27157
27477
  }
27158
27478
  static async retry(id, scope = {}) {
27159
- const current = await this.getById(id, scope);
27160
- if (!current) return null;
27161
- if (current.status !== "failed" && current.status !== "dead_letter") return null;
27162
- const conditions = [eq(tasks2.id, id), ...this.buildScopeWhere(scope)];
27163
- const result = await db.update(tasks2).set({
27164
- status: "pending",
27165
- startedAt: null,
27166
- finishedAt: null,
27167
- retryAfter: null,
27168
- retryCount: 0
27169
- }).where(and(...conditions)).returning();
27170
- return result[0] || null;
27171
- }
27172
- static async retryBatch(batchId, scope = {}) {
27173
27479
  const conditions = [
27174
- eq(tasks2.batchId, batchId),
27480
+ eq(tasks2.id, id),
27175
27481
  or(eq(tasks2.status, "failed"), eq(tasks2.status, "dead_letter")),
27176
27482
  ...this.buildScopeWhere(scope)
27177
27483
  ];
27178
- const result = await db.update(tasks2).set({
27484
+ return db.transaction((tx) => tx.update(tasks2).set({
27179
27485
  status: "pending",
27180
27486
  startedAt: null,
27181
27487
  finishedAt: null,
27182
27488
  retryAfter: null,
27183
27489
  retryCount: 0
27184
- }).where(and(...conditions)).returning();
27185
- return result.length;
27490
+ }).where(and(...conditions, hasViableDependency())).returning().get() ?? null, { behavior: "immediate" });
27491
+ }
27492
+ static async retryBatch(batchId, scope = {}) {
27493
+ const sqlite2 = getSqlite();
27494
+ const scopeFilter = scope.cwd === void 0 ? "" : "AND candidate.cwd = ?";
27495
+ const parameters = scope.cwd === void 0 ? [batchId] : [batchId, scope.cwd];
27496
+ return db.transaction(() => sqlite2.query(`
27497
+ WITH RECURSIVE
27498
+ candidate(id, cwd, depends_on) AS MATERIALIZED (
27499
+ SELECT candidate.id, candidate.cwd, candidate.depends_on
27500
+ FROM tasks AS candidate
27501
+ WHERE candidate.batch_id = ?
27502
+ AND candidate.status IN ('failed', 'dead_letter')
27503
+ ${scopeFilter}
27504
+ ),
27505
+ retryable(id, cwd, depends_on) AS (
27506
+ SELECT candidate.id,
27507
+ candidate.cwd,
27508
+ candidate.depends_on
27509
+ FROM candidate
27510
+ WHERE candidate.depends_on IS NULL
27511
+ OR (
27512
+ NOT EXISTS (
27513
+ SELECT 1 FROM candidate AS internal_parent
27514
+ WHERE internal_parent.id = candidate.depends_on
27515
+ )
27516
+ AND EXISTS (
27517
+ SELECT 1 FROM tasks AS external_parent
27518
+ WHERE external_parent.id = candidate.depends_on
27519
+ AND external_parent.cwd IS candidate.cwd
27520
+ AND (
27521
+ external_parent.status IN ('pending', 'running', 'done')
27522
+ OR (
27523
+ external_parent.status = 'failed'
27524
+ AND external_parent.retry_count <= external_parent.max_retries
27525
+ )
27526
+ )
27527
+ )
27528
+ )
27529
+ UNION
27530
+ SELECT child.id,
27531
+ child.cwd,
27532
+ child.depends_on
27533
+ FROM candidate AS child
27534
+ INNER JOIN retryable AS parent
27535
+ ON child.depends_on = parent.id
27536
+ AND child.cwd IS parent.cwd
27537
+ )
27538
+ UPDATE tasks
27539
+ SET status = 'pending',
27540
+ started_at = NULL,
27541
+ finished_at = NULL,
27542
+ retry_after = NULL,
27543
+ retry_count = 0
27544
+ WHERE id IN (SELECT id FROM retryable)
27545
+ `).run(...parameters).changes, { behavior: "immediate" });
27186
27546
  }
27187
27547
  static async getById(id, scope = {}) {
27188
27548
  const conditions = [eq(tasks2.id, id), ...this.buildScopeWhere(scope)];
@@ -27247,23 +27607,129 @@ var TaskService = class {
27247
27607
  return stats;
27248
27608
  }
27249
27609
  static async delete(id, scope = {}) {
27250
- const conditions = [eq(tasks2.id, id), ...this.buildScopeWhere(scope)];
27251
- const existing = await db.select({ id: tasks2.id }).from(tasks2).where(and(...conditions)).limit(1);
27252
- if (!existing[0]) return false;
27253
- await db.delete(taskRuns2).where(eq(taskRuns2.taskId, id));
27610
+ const conditions = [
27611
+ eq(tasks2.id, id),
27612
+ sql`${tasks2.status} <> 'running'`,
27613
+ sql`NOT EXISTS (
27614
+ SELECT 1 FROM ${taskRuns2}
27615
+ WHERE ${taskRuns2.taskId} = ${tasks2.id}
27616
+ AND ${taskRuns2.status} = 'running'
27617
+ )`,
27618
+ hasNoExecutableDependents(),
27619
+ ...this.buildScopeWhere(scope)
27620
+ ];
27254
27621
  const result = await db.delete(tasks2).where(and(...conditions)).returning();
27255
- return result.length > 0;
27622
+ if (result.length > 0) {
27623
+ await db.delete(taskRuns2).where(eq(taskRuns2.taskId, id));
27624
+ return true;
27625
+ }
27626
+ if (!await this.getById(id, scope)) return false;
27627
+ const dependent = await db.select({ id: tasks2.id }).from(tasks2).where(and(
27628
+ eq(tasks2.dependsOn, id),
27629
+ sql`${tasks2.status} IN ('pending', 'running', 'failed', 'dead_letter')`
27630
+ )).orderBy(asc(tasks2.id)).limit(1);
27631
+ if (dependent[0]) {
27632
+ throw new TaskDeletionConflictError(
27633
+ `\u4EFB\u52A1 #${id} \u4ECD\u88AB\u53EF\u6267\u884C\u4EFB\u52A1 #${dependent[0].id} \u4F9D\u8D56\uFF0C\u8BF7\u5148\u5904\u7406\u4F9D\u8D56\u4EFB\u52A1`
27634
+ );
27635
+ }
27636
+ throw new TaskDeletionConflictError(
27637
+ `\u4EFB\u52A1 #${id} \u6B63\u5728\u8FD0\u884C\uFF0C\u8BF7\u5148\u53D6\u6D88\u4EFB\u52A1\u5E76\u7B49\u5F85\u6267\u884C\u8FDB\u7A0B\u9000\u51FA`
27638
+ );
27256
27639
  }
27257
- static async deleteOlderThan(retentionDays) {
27640
+ static async deleteOlderThan(retentionDays, shouldStop = () => false) {
27258
27641
  const cutoffSec = Math.floor(Date.now() / 1e3) - retentionDays * 86400;
27259
- const result = await db.delete(tasks2).where(
27260
- and(
27261
- sql`${tasks2.status} IN ('done', 'failed', 'dead_letter')`,
27262
- sql`${tasks2.finishedAt} IS NOT NULL`,
27263
- sql`${tasks2.finishedAt} < ${cutoffSec}`
27264
- )
27265
- ).returning();
27266
- return result.length;
27642
+ const batchSize = 500;
27643
+ const sqlite2 = getSqlite();
27644
+ cleanupInvocation += 1;
27645
+ const candidateTable = `cleanup_candidates_${process.pid}_${cleanupInvocation}`;
27646
+ let deletedTotal = 0;
27647
+ sqlite2.exec(`
27648
+ CREATE TEMP TABLE ${candidateTable} (
27649
+ id INTEGER NOT NULL PRIMARY KEY
27650
+ ) WITHOUT ROWID;
27651
+ `);
27652
+ try {
27653
+ let ceilingId = null;
27654
+ while (true) {
27655
+ if (shouldStop()) return deletedTotal;
27656
+ const batch = db.transaction(() => {
27657
+ sqlite2.query(`DELETE FROM ${candidateTable}`).run();
27658
+ const ceilingPredicate = ceilingId == null ? "" : "AND candidate.id < ?";
27659
+ const rawCandidateStatement = sqlite2.query(`
27660
+ INSERT INTO ${candidateTable}(id)
27661
+ SELECT candidate.id
27662
+ FROM tasks AS candidate NOT INDEXED
27663
+ WHERE candidate.status IN ('done', 'dead_letter', 'cancelled')
27664
+ AND candidate.finished_at IS NOT NULL
27665
+ AND candidate.finished_at < ?
27666
+ ${ceilingPredicate}
27667
+ AND NOT EXISTS (
27668
+ SELECT 1 FROM task_runs AS active_run
27669
+ WHERE active_run.task_id = candidate.id
27670
+ AND active_run.status = 'running'
27671
+ )
27672
+ ORDER BY candidate.id DESC
27673
+ LIMIT ?
27674
+ `);
27675
+ const rawCount = ceilingId == null ? rawCandidateStatement.run(cutoffSec, batchSize).changes : rawCandidateStatement.run(cutoffSec, ceilingId, batchSize).changes;
27676
+ if (rawCount === 0) return { deleted: 0, nextCeilingId: null };
27677
+ const rawPage = sqlite2.query(`
27678
+ SELECT min(id) AS nextCeilingId FROM ${candidateTable}
27679
+ `).get();
27680
+ sqlite2.query(`
27681
+ DELETE FROM ${candidateTable}
27682
+ WHERE EXISTS (
27683
+ SELECT 1 FROM tasks AS anomalous
27684
+ WHERE anomalous.id = ${candidateTable}.id
27685
+ AND anomalous.depends_on IS NOT NULL
27686
+ AND anomalous.depends_on >= anomalous.id
27687
+ )
27688
+ `).run();
27689
+ while (true) {
27690
+ const pruned = sqlite2.query(`
27691
+ DELETE FROM ${candidateTable}
27692
+ WHERE EXISTS (
27693
+ SELECT 1 FROM tasks AS dependent_task
27694
+ WHERE dependent_task.depends_on = ${candidateTable}.id
27695
+ AND NOT EXISTS (
27696
+ SELECT 1 FROM ${candidateTable} AS selected_dependent
27697
+ WHERE selected_dependent.id = dependent_task.id
27698
+ )
27699
+ )
27700
+ `).run().changes;
27701
+ if (pruned === 0) break;
27702
+ }
27703
+ const selected = sqlite2.query(`
27704
+ SELECT count(*) AS count FROM ${candidateTable}
27705
+ `).get();
27706
+ if (selected.count === 0) {
27707
+ return { deleted: 0, nextCeilingId: rawPage.nextCeilingId };
27708
+ }
27709
+ sqlite2.query(`
27710
+ DELETE FROM tasks
27711
+ WHERE id IN (SELECT id FROM ${candidateTable})
27712
+ `).run();
27713
+ const remaining = sqlite2.query(`
27714
+ SELECT count(*) AS count
27715
+ FROM tasks
27716
+ WHERE id IN (SELECT id FROM ${candidateTable})
27717
+ `).get();
27718
+ if (remaining.count !== 0) {
27719
+ throw new Error("\u5386\u53F2\u6E05\u7406\u5019\u9009\u5728\u540C\u4E00\u5199\u4E8B\u52A1\u5185\u53D1\u751F\u6F02\u79FB\uFF0C\u5DF2\u56DE\u6EDA\u672C\u6279\u5220\u9664");
27720
+ }
27721
+ return { deleted: selected.count, nextCeilingId: rawPage.nextCeilingId };
27722
+ }, { behavior: "immediate" });
27723
+ if (batch.nextCeilingId == null) break;
27724
+ ceilingId = batch.nextCeilingId;
27725
+ deletedTotal += batch.deleted;
27726
+ await Bun.sleep(0);
27727
+ if (shouldStop()) return deletedTotal;
27728
+ }
27729
+ return deletedTotal;
27730
+ } finally {
27731
+ sqlite2.exec(`DROP TABLE IF EXISTS ${candidateTable}`);
27732
+ }
27267
27733
  }
27268
27734
  };
27269
27735
 
@@ -27294,19 +27760,17 @@ var TaskTemplateService = class {
27294
27760
  static async create(data) {
27295
27761
  this.validate(data);
27296
27762
  const now = Date.now();
27297
- const result = await db.insert(taskTemplates2).values({ ...data, createdAt: now, updatedAt: now }).returning();
27298
- const tmpl = result[0];
27299
- if (tmpl.nextRunAt == null) {
27300
- const nextRunAt = this.calculateNextRunAt(
27301
- tmpl.scheduleType,
27302
- tmpl
27303
- );
27304
- if (nextRunAt != null) {
27305
- await db.update(taskTemplates2).set({ nextRunAt }).where(eq(taskTemplates2.id, tmpl.id));
27306
- tmpl.nextRunAt = nextRunAt;
27307
- }
27308
- }
27309
- return tmpl;
27763
+ const nextRunAt = data.nextRunAt ?? this.calculateNextRunAt(
27764
+ data.scheduleType,
27765
+ {
27766
+ cronExpr: data.cronExpr ?? null,
27767
+ intervalMs: data.intervalMs ?? null,
27768
+ runAt: data.runAt ?? null
27769
+ },
27770
+ now
27771
+ );
27772
+ const result = await db.insert(taskTemplates2).values({ ...data, nextRunAt, createdAt: now, updatedAt: now }).returning();
27773
+ return result[0];
27310
27774
  }
27311
27775
  static validate(data) {
27312
27776
  if (!data.name.trim()) throw new Error("name \u4E0D\u80FD\u4E3A\u7A7A");
@@ -27346,8 +27810,18 @@ var TaskTemplateService = class {
27346
27810
  return result[0] || null;
27347
27811
  }
27348
27812
  static async enable(id) {
27349
- const result = await db.update(taskTemplates2).set({ enabled: true, updatedAt: Date.now() }).where(eq(taskTemplates2.id, id)).returning();
27350
- return result[0] || null;
27813
+ return db.transaction((tx) => {
27814
+ const template = tx.select().from(taskTemplates2).where(eq(taskTemplates2.id, id)).limit(1).get();
27815
+ if (!template) return null;
27816
+ const nextRunAt = template.nextRunAt ?? this.calculateNextRunAt(
27817
+ template.scheduleType,
27818
+ template
27819
+ );
27820
+ if (nextRunAt == null) {
27821
+ throw new Error(`\u6A21\u677F #${id} \u65E0\u6CD5\u8BA1\u7B97\u4E0B\u4E00\u6B21\u6267\u884C\u65F6\u95F4\uFF0C\u5DF2\u4FDD\u6301\u7981\u7528`);
27822
+ }
27823
+ return tx.update(taskTemplates2).set({ enabled: true, nextRunAt, updatedAt: Date.now() }).where(eq(taskTemplates2.id, id)).returning().get() ?? null;
27824
+ }, { behavior: "immediate" });
27351
27825
  }
27352
27826
  static async disable(id) {
27353
27827
  const result = await db.update(taskTemplates2).set({ enabled: false, updatedAt: Date.now() }).where(eq(taskTemplates2.id, id)).returning();
@@ -27357,6 +27831,47 @@ var TaskTemplateService = class {
27357
27831
  const result = await db.delete(taskTemplates2).where(eq(taskTemplates2.id, id)).returning();
27358
27832
  return result.length > 0;
27359
27833
  }
27834
+ static async deleteExpiredDelayed(retentionDays, shouldStop = () => false) {
27835
+ const cutoffMs = Date.now() - retentionDays * 864e5;
27836
+ const batchSize = 500;
27837
+ let deletedTotal = 0;
27838
+ while (!shouldStop()) {
27839
+ const deleted = db.transaction((tx) => tx.delete(taskTemplates2).where(and(
27840
+ sql`${taskTemplates2.id} IN (
27841
+ SELECT candidate.id
27842
+ FROM task_templates AS candidate
27843
+ WHERE candidate.schedule_type = 'delayed'
27844
+ AND candidate.enabled = 0
27845
+ AND candidate.last_run_at IS NOT NULL
27846
+ AND candidate.last_run_at < ${cutoffMs}
27847
+ AND NOT EXISTS (
27848
+ SELECT 1 FROM tasks AS active_task
27849
+ WHERE active_task.template_id = candidate.id
27850
+ AND (
27851
+ active_task.status IN ('pending', 'running')
27852
+ OR (
27853
+ active_task.status = 'failed'
27854
+ AND active_task.retry_count <= active_task.max_retries
27855
+ )
27856
+ )
27857
+ )
27858
+ AND NOT EXISTS (
27859
+ SELECT 1
27860
+ FROM task_runs AS active_run
27861
+ INNER JOIN tasks AS run_task ON run_task.id = active_run.task_id
27862
+ WHERE run_task.template_id = candidate.id
27863
+ AND active_run.status = 'running'
27864
+ )
27865
+ ORDER BY candidate.last_run_at, candidate.id
27866
+ LIMIT ${batchSize}
27867
+ )`
27868
+ )).returning({ id: taskTemplates2.id }).all().length, { behavior: "immediate" });
27869
+ if (deleted === 0) break;
27870
+ deletedTotal += deleted;
27871
+ await Bun.sleep(0);
27872
+ }
27873
+ return deletedTotal;
27874
+ }
27360
27875
  static calculateNextRunAt(scheduleType, template, afterMs) {
27361
27876
  const base = afterMs ?? Date.now();
27362
27877
  switch (scheduleType) {
@@ -27406,13 +27921,73 @@ function parseDuration(input) {
27406
27921
  return null;
27407
27922
  }
27408
27923
 
27924
+ // src/core/semver.ts
27925
+ function parseSemanticVersion(version3) {
27926
+ const match = /^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-([0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*))?(?:\+[0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*)?$/.exec(version3);
27927
+ if (!match) return null;
27928
+ const prerelease = match[4]?.split(".") ?? [];
27929
+ if (prerelease.some((identifier) => /^\d+$/.test(identifier) && identifier.length > 1 && identifier.startsWith("0"))) {
27930
+ return null;
27931
+ }
27932
+ return {
27933
+ major: Number(match[1]),
27934
+ minor: Number(match[2]),
27935
+ patch: Number(match[3]),
27936
+ prerelease
27937
+ };
27938
+ }
27939
+ function compareSemanticVersions(left, right) {
27940
+ const a = parseSemanticVersion(left);
27941
+ const b = parseSemanticVersion(right);
27942
+ if (!a || !b) return null;
27943
+ for (const key of ["major", "minor", "patch"]) {
27944
+ if (a[key] !== b[key]) return a[key] - b[key];
27945
+ }
27946
+ if (a.prerelease.length === 0 || b.prerelease.length === 0) {
27947
+ if (a.prerelease.length === b.prerelease.length) return 0;
27948
+ return a.prerelease.length === 0 ? 1 : -1;
27949
+ }
27950
+ const identifiers = Math.max(a.prerelease.length, b.prerelease.length);
27951
+ for (let index2 = 0; index2 < identifiers; index2 += 1) {
27952
+ const leftIdentifier = a.prerelease[index2];
27953
+ const rightIdentifier = b.prerelease[index2];
27954
+ if (leftIdentifier === void 0 || rightIdentifier === void 0) {
27955
+ return leftIdentifier === rightIdentifier ? 0 : leftIdentifier === void 0 ? -1 : 1;
27956
+ }
27957
+ if (leftIdentifier === rightIdentifier) continue;
27958
+ const leftNumeric = /^\d+$/.test(leftIdentifier);
27959
+ const rightNumeric = /^\d+$/.test(rightIdentifier);
27960
+ if (leftNumeric && rightNumeric) return Number(leftIdentifier) - Number(rightIdentifier);
27961
+ if (leftNumeric !== rightNumeric) return leftNumeric ? -1 : 1;
27962
+ return leftIdentifier < rightIdentifier ? -1 : 1;
27963
+ }
27964
+ return 0;
27965
+ }
27966
+ function isSemanticVersion(version3) {
27967
+ return parseSemanticVersion(version3) !== null;
27968
+ }
27969
+
27970
+ // src/core/launch-protocol.ts
27971
+ var MANAGED_RUN_ENV = "SUPERTASK_MANAGED_RUN";
27972
+ var MANAGED_RUN_ENV_VALUE = "1";
27973
+
27409
27974
  // src/daemon/pm2.ts
27410
27975
  import { execSync, spawnSync } from "child_process";
27411
- import { chmodSync, existsSync as existsSync3, mkdirSync as mkdirSync2, readFileSync as readFileSync2, writeFileSync } from "fs";
27412
- import { homedir as homedir3 } from "os";
27413
- import { dirname as dirname2, isAbsolute, join as join3 } from "path";
27414
- import { fileURLToPath as fileURLToPath2 } from "url";
27415
- import { Database as Database3 } from "bun:sqlite";
27976
+ import {
27977
+ accessSync,
27978
+ chmodSync,
27979
+ constants,
27980
+ existsSync as existsSync4,
27981
+ mkdirSync as mkdirSync3,
27982
+ readFileSync as readFileSync3,
27983
+ rmSync,
27984
+ statSync,
27985
+ writeFileSync
27986
+ } from "fs";
27987
+ import { homedir as homedir3, userInfo } from "os";
27988
+ import { delimiter, dirname as dirname4, isAbsolute, join as join4, resolve } from "path";
27989
+ import { fileURLToPath as fileURLToPath3 } from "url";
27990
+ import { Database as Database4 } from "bun:sqlite";
27416
27991
 
27417
27992
  // src/gateway/config.ts
27418
27993
  import { existsSync as existsSync2, readFileSync } from "fs";
@@ -27529,80 +28104,223 @@ function loadConfig(path = getConfigPath()) {
27529
28104
  }
27530
28105
  }
27531
28106
 
27532
- // src/daemon/pm2.ts
27533
- var __dirname = dirname2(fileURLToPath2(import.meta.url));
27534
- var PROCESS_NAME = "supertask-gateway";
27535
- var GATEWAY_LOCK_STALE_MS = 3e4;
28107
+ // src/core/package-version.ts
28108
+ import { existsSync as existsSync3, readFileSync as readFileSync2 } from "fs";
28109
+ import { dirname as dirname2, join as join3 } from "path";
28110
+ import { fileURLToPath as fileURLToPath2 } from "url";
27536
28111
  function getPackageVersion() {
27537
- const envVersion = process.env.npm_package_version;
27538
- if (envVersion) return envVersion;
28112
+ let directory = dirname2(fileURLToPath2(import.meta.url));
28113
+ for (let depth = 0; depth < 5; depth += 1) {
28114
+ const packagePath = join3(directory, "package.json");
28115
+ if (existsSync3(packagePath)) {
28116
+ try {
28117
+ const pkg = JSON.parse(readFileSync2(packagePath, "utf8"));
28118
+ if (typeof pkg.version === "string") return pkg.version;
28119
+ } catch {
28120
+ return "0.0.0";
28121
+ }
28122
+ }
28123
+ directory = dirname2(directory);
28124
+ }
28125
+ return "0.0.0";
28126
+ }
28127
+
28128
+ // src/daemon/management-lock.ts
28129
+ import { Database as Database3 } from "bun:sqlite";
28130
+ import { mkdirSync as mkdirSync2 } from "fs";
28131
+ import { dirname as dirname3 } from "path";
28132
+ var ManagementLockBusyError = class extends Error {
28133
+ constructor() {
28134
+ super("Gateway management lock is busy");
28135
+ this.name = "ManagementLockBusyError";
28136
+ }
28137
+ };
28138
+ function isBusyError(error45) {
28139
+ if (!(error45 instanceof Error)) return false;
28140
+ const code = error45.code;
28141
+ return code === "SQLITE_BUSY" || /database is locked/i.test(error45.message);
28142
+ }
28143
+ function withExclusiveManagementLock(path, timeoutMs, action) {
28144
+ mkdirSync2(dirname3(path), { recursive: true });
28145
+ const database = new Database3(path, { create: true });
28146
+ const normalizedTimeout = Number.isFinite(timeoutMs) && timeoutMs >= 0 ? Math.floor(timeoutMs) : 0;
28147
+ database.exec(`PRAGMA busy_timeout = ${normalizedTimeout}`);
27539
28148
  try {
27540
- const pkgPath = join3(__dirname, "../../package.json");
27541
- const pkg = JSON.parse(readFileSync2(pkgPath, "utf-8"));
27542
- return typeof pkg.version === "string" ? pkg.version : "0.0.0";
27543
- } catch {
27544
- return "0.0.0";
28149
+ try {
28150
+ database.exec("BEGIN IMMEDIATE");
28151
+ } catch (error45) {
28152
+ if (isBusyError(error45)) throw new ManagementLockBusyError();
28153
+ throw error45;
28154
+ }
28155
+ try {
28156
+ const result = action();
28157
+ database.exec("COMMIT");
28158
+ return result;
28159
+ } catch (error45) {
28160
+ try {
28161
+ database.exec("ROLLBACK");
28162
+ } catch {
28163
+ }
28164
+ throw error45;
28165
+ }
28166
+ } finally {
28167
+ database.close();
27545
28168
  }
27546
28169
  }
28170
+
28171
+ // src/daemon/pm2.ts
28172
+ var __dirname = dirname4(fileURLToPath3(import.meta.url));
28173
+ var PROCESS_NAME = "supertask-gateway";
28174
+ var MAC_LAUNCH_AGENT_LABEL = "com.supertask.pm2-resurrect";
28175
+ var GATEWAY_LOCK_STALE_MS = 3e4;
27547
28176
  function resolveGatewayEntry() {
27548
28177
  const override = process.env.SUPERTASK_GATEWAY_ENTRY;
27549
28178
  if (override) {
27550
- if (!existsSync3(override)) throw new Error(`[supertask] Gateway entry not found: ${override}`);
28179
+ if (!existsSync4(override)) throw new Error(`[supertask] Gateway entry not found: ${override}`);
27551
28180
  return override;
27552
28181
  }
27553
28182
  const candidates = [
27554
- join3(__dirname, "../gateway/index.js"),
27555
- join3(__dirname, "../gateway/index.ts")
28183
+ join4(__dirname, "../gateway/index.js"),
28184
+ join4(__dirname, "../gateway/index.ts")
27556
28185
  ];
27557
- const entry = candidates.find((candidate) => existsSync3(candidate));
28186
+ const entry = candidates.find((candidate) => existsSync4(candidate));
27558
28187
  if (!entry) throw new Error(`[supertask] Gateway entry not found. Checked: ${candidates.join(", ")}`);
27559
28188
  return entry;
27560
28189
  }
27561
- function versionFile() {
27562
- return process.env.SUPERTASK_VERSION_FILE ?? join3(homedir3(), ".local/share/opencode/supertask-gateway-version");
28190
+ function runtimeHome(env) {
28191
+ return resolve(env.HOME || homedir3());
28192
+ }
28193
+ function runtimePath(value, cwd) {
28194
+ return resolve(cwd, value);
27563
28195
  }
27564
- function getRunningVersion() {
28196
+ function versionFile(env = process.env, cwd = process.cwd()) {
28197
+ return env.SUPERTASK_VERSION_FILE ? runtimePath(env.SUPERTASK_VERSION_FILE, cwd) : join4(runtimeHome(env), ".local/share/opencode/supertask-gateway-version");
28198
+ }
28199
+ function getRunningVersion(env = process.env, cwd = process.cwd()) {
27565
28200
  try {
27566
- const path = versionFile();
27567
- if (!existsSync3(path)) return null;
27568
- return readFileSync2(path, "utf-8").trim() || null;
28201
+ const path = versionFile(env, cwd);
28202
+ if (!existsSync4(path)) return null;
28203
+ return readFileSync3(path, "utf-8").trim() || null;
27569
28204
  } catch {
27570
28205
  return null;
27571
28206
  }
27572
28207
  }
27573
- function writeRunningVersion(version3) {
27574
- const path = versionFile();
27575
- mkdirSync2(dirname2(path), { recursive: true });
28208
+ function resolveRuntimeExecutable(command, env, cwd) {
28209
+ if (isAbsolute(command) || command.includes("/")) return runtimePath(command, cwd);
28210
+ for (const entry of (env.PATH ?? "").split(delimiter)) {
28211
+ if (!entry) continue;
28212
+ const candidate = resolve(cwd, entry, command);
28213
+ try {
28214
+ accessSync(candidate, constants.X_OK);
28215
+ return candidate;
28216
+ } catch {
28217
+ }
28218
+ }
28219
+ return command;
28220
+ }
28221
+ function runtimeScope(runtime) {
28222
+ const { cwd, env } = runtime;
28223
+ const home = runtimeHome(env);
28224
+ return {
28225
+ cwd: resolve(cwd),
28226
+ databasePath: env.SUPERTASK_DB_PATH ? runtimePath(env.SUPERTASK_DB_PATH, cwd) : join4(home, ".local/share/opencode/tasks.db"),
28227
+ configPath: env.SUPERTASK_CONFIG_PATH ? runtimePath(env.SUPERTASK_CONFIG_PATH, cwd) : join4(home, ".config/opencode/supertask.json"),
28228
+ opencodePath: resolveRuntimeExecutable(env.SUPERTASK_OPENCODE_BIN ?? "opencode", env, cwd),
28229
+ home,
28230
+ pm2Home: env.PM2_HOME ? runtimePath(env.PM2_HOME, cwd) : join4(home, ".pm2"),
28231
+ managementLockPath: canonicalManagementLockPath(env, cwd)
28232
+ };
28233
+ }
28234
+ function scopesMatch(left, right) {
28235
+ return left.databasePath === right.databasePath && left.configPath === right.configPath && left.opencodePath === right.opencodePath && left.home === right.home && left.pm2Home === right.pm2Home && left.managementLockPath === right.managementLockPath;
28236
+ }
28237
+ function currentScope() {
28238
+ return runtimeScope({ cwd: process.cwd(), env: process.env });
28239
+ }
28240
+ function writeRunningVersion(version3, env = process.env, cwd = process.cwd()) {
28241
+ const path = versionFile(env, cwd);
28242
+ mkdirSync3(dirname4(path), { recursive: true });
27576
28243
  writeFileSync(path, version3, "utf-8");
27577
28244
  }
27578
- function pm2Bin() {
27579
- return process.env.SUPERTASK_PM2_BIN ?? (process.platform === "win32" ? "pm2.cmd" : "pm2");
28245
+ function pm2Bin(env = process.env) {
28246
+ return env.SUPERTASK_PM2_BIN ?? (process.platform === "win32" ? "pm2.cmd" : "pm2");
27580
28247
  }
27581
- function isPm2Installed() {
27582
- const result = spawnSync(pm2Bin(), ["--version"], {
28248
+ function pm2CommandTimeoutMs(env = process.env) {
28249
+ const configured = Number(env.SUPERTASK_PM2_COMMAND_TIMEOUT_MS ?? 15e3);
28250
+ return Number.isFinite(configured) && configured > 0 ? configured : 15e3;
28251
+ }
28252
+ function resolvePm2Bin() {
28253
+ const configured = pm2Bin();
28254
+ if (isAbsolute(configured)) return configured;
28255
+ const result = spawnSync("which", [configured], { encoding: "utf8" });
28256
+ const resolved = result.status === 0 ? result.stdout.trim().split("\n")[0] : "";
28257
+ if (!resolved) throw new Error(`[supertask] \u65E0\u6CD5\u89E3\u6790 pm2 \u53EF\u6267\u884C\u6587\u4EF6: ${configured}`);
28258
+ return resolved;
28259
+ }
28260
+ function launchAgentPath() {
28261
+ return process.env.SUPERTASK_LAUNCH_AGENT_PATH ?? join4(runtimeHome(process.env), "Library/LaunchAgents", `${MAC_LAUNCH_AGENT_LABEL}.plist`);
28262
+ }
28263
+ function xmlUnescape(value) {
28264
+ return value.replaceAll("&apos;", "'").replaceAll("&quot;", '"').replaceAll("&gt;", ">").replaceAll("&lt;", "<").replaceAll("&amp;", "&");
28265
+ }
28266
+ function plistValue(contents, key) {
28267
+ const escapedKey = key.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
28268
+ const match = contents.match(new RegExp(`<key>\\s*${escapedKey}\\s*</key>\\s*<string>([^<]*)</string>`));
28269
+ return match?.[1] ? xmlUnescape(match[1]) : null;
28270
+ }
28271
+ function isPm2Installed(env = process.env) {
28272
+ const result = spawnSync(pm2Bin(env), ["--version"], {
27583
28273
  stdio: "ignore",
27584
- shell: process.platform === "win32"
28274
+ env,
28275
+ shell: process.platform === "win32",
28276
+ timeout: pm2CommandTimeoutMs(env),
28277
+ killSignal: "SIGKILL"
27585
28278
  });
27586
28279
  return result.status === 0;
27587
28280
  }
27588
- function pm2Exec(args) {
27589
- const result = spawnSync(pm2Bin(), args, {
28281
+ function resolveCommand(command) {
28282
+ const lookup = process.platform === "win32" ? "where" : "which";
28283
+ const result = spawnSync(lookup, [command], { encoding: "utf8" });
28284
+ return result.status === 0 ? result.stdout.trim().split("\n")[0] || null : null;
28285
+ }
28286
+ function npmPreferredEnvironment() {
28287
+ if (process.platform === "win32") return null;
28288
+ const npm = resolveCommand("npm");
28289
+ const node = resolveCommand("node");
28290
+ if (!npm || !node) return null;
28291
+ const command = resolvePm2Bin();
28292
+ const path = [.../* @__PURE__ */ new Set([
28293
+ dirname4(command),
28294
+ dirname4(npm),
28295
+ dirname4(node),
28296
+ "/usr/local/bin",
28297
+ "/usr/bin",
28298
+ "/bin"
28299
+ ])].join(delimiter);
28300
+ return { command, env: { ...process.env, PATH: path } };
28301
+ }
28302
+ function pm2Exec(args, options = {}) {
28303
+ const npmEnvironment = options.preferNpm ? npmPreferredEnvironment() : null;
28304
+ const effectiveEnv = options.env ?? npmEnvironment?.env ?? process.env;
28305
+ const result = spawnSync(npmEnvironment?.command ?? pm2Bin(effectiveEnv), args, {
27590
28306
  stdio: ["pipe", "pipe", "pipe"],
27591
28307
  encoding: "utf-8",
27592
- env: process.env,
27593
- shell: process.platform === "win32"
28308
+ env: effectiveEnv,
28309
+ shell: process.platform === "win32",
28310
+ timeout: options.timeoutMs ?? pm2CommandTimeoutMs(effectiveEnv),
28311
+ killSignal: "SIGKILL"
27594
28312
  });
27595
28313
  const output = `${result.stdout ?? ""}${result.stderr ?? ""}`.trim();
27596
28314
  if (result.error) return { ok: false, output: result.error.message };
27597
28315
  return { ok: result.status === 0, output };
27598
28316
  }
27599
- function requirePm2(args, action) {
27600
- const result = pm2Exec(args);
28317
+ function requirePm2(args, action, options = {}) {
28318
+ const result = pm2Exec(args, options);
27601
28319
  if (!result.ok) throw new Error(`[supertask] ${action} failed: ${result.output || "unknown pm2 error"}`);
27602
28320
  return result.output;
27603
28321
  }
27604
- function pm2JsonList() {
27605
- const output = requirePm2(["jlist"], "pm2 jlist");
28322
+ function pm2JsonList(env = process.env) {
28323
+ const output = requirePm2(["jlist"], "pm2 jlist", { env });
27606
28324
  try {
27607
28325
  const parsed = JSON.parse(output);
27608
28326
  if (!Array.isArray(parsed)) throw new Error("result is not an array");
@@ -27612,15 +28330,69 @@ function pm2JsonList() {
27612
28330
  throw new Error(`[supertask] Invalid pm2 jlist output: ${message}`);
27613
28331
  }
27614
28332
  }
27615
- function databasePath() {
27616
- return process.env.SUPERTASK_DB_PATH ?? join3(homedir3(), ".local/share/opencode/tasks.db");
28333
+ function gatewayEntryFromProcess(processInfo) {
28334
+ const args = processInfo?.pm2_env?.args ?? processInfo?.args;
28335
+ const candidates = Array.isArray(args) ? [...args].reverse() : typeof args === "string" ? [args] : [];
28336
+ const savedCwd = processInfo?.pm2_env?.pm_cwd;
28337
+ for (const candidate of candidates) {
28338
+ const path = typeof savedCwd === "string" ? runtimePath(candidate, savedCwd) : candidate;
28339
+ if (existsSync4(path)) return resolve(path);
28340
+ }
28341
+ return null;
27617
28342
  }
27618
- function isGatewayReady(expectedPid) {
27619
- const path = databasePath();
27620
- if (!existsSync3(path)) return false;
28343
+ function gatewayEnvironmentFromProcess(processInfo) {
28344
+ const saved = processInfo?.pm2_env?.env ?? processInfo?.env;
28345
+ if (!saved) return { ...process.env };
28346
+ const env = {};
28347
+ for (const [key, value] of Object.entries(saved)) {
28348
+ if (typeof value === "string") env[key] = value;
28349
+ }
28350
+ return env;
28351
+ }
28352
+ function gatewayRuntimeFromProcess(processInfo) {
28353
+ const gatewayEntry = gatewayEntryFromProcess(processInfo);
28354
+ if (!gatewayEntry) return null;
28355
+ const savedBunPath = processInfo?.pm2_env?.pm_exec_path ?? processInfo?.pm_exec_path;
28356
+ const savedCwd = processInfo?.pm2_env?.pm_cwd;
28357
+ const savedEnv = gatewayEnvironmentFromProcess(processInfo);
28358
+ if (typeof savedBunPath !== "string" || typeof savedCwd !== "string") return null;
28359
+ try {
28360
+ accessSync(savedBunPath, constants.X_OK);
28361
+ if (!statSync(savedCwd).isDirectory()) return null;
28362
+ if (spawnSync(savedBunPath, ["--version"], {
28363
+ stdio: "ignore",
28364
+ env: savedEnv,
28365
+ timeout: pm2CommandTimeoutMs(savedEnv),
28366
+ killSignal: "SIGKILL"
28367
+ }).status !== 0) return null;
28368
+ } catch {
28369
+ return null;
28370
+ }
28371
+ const killTimeout = processInfo?.pm2_env?.kill_timeout ?? processInfo?.kill_timeout;
28372
+ return {
28373
+ gatewayEntry,
28374
+ bunPath: savedBunPath,
28375
+ cwd: resolve(savedCwd),
28376
+ env: savedEnv,
28377
+ killTimeoutMs: Number.isInteger(killTimeout) && killTimeout >= 5e3 ? killTimeout : void 0
28378
+ };
28379
+ }
28380
+ function currentGatewayRuntime(gatewayEntry = resolveGatewayEntry()) {
28381
+ return {
28382
+ gatewayEntry: resolve(gatewayEntry),
28383
+ bunPath: resolve(findBunPath()),
28384
+ cwd: process.cwd(),
28385
+ env: { ...process.env }
28386
+ };
28387
+ }
28388
+ function databasePath(env = process.env, cwd = process.cwd()) {
28389
+ return env.SUPERTASK_DB_PATH ? runtimePath(env.SUPERTASK_DB_PATH, cwd) : join4(runtimeHome(env), ".local/share/opencode/tasks.db");
28390
+ }
28391
+ function isGatewayReady(expectedPid, path = databasePath()) {
28392
+ if (!existsSync4(path)) return false;
27621
28393
  let database = null;
27622
28394
  try {
27623
- database = new Database3(path, { readonly: true });
28395
+ database = new Database4(path, { readonly: true });
27624
28396
  const row = database.query(
27625
28397
  "SELECT pid, heartbeat_at, ready_at FROM gateway_lock WHERE id = 1"
27626
28398
  ).get();
@@ -27634,18 +28406,34 @@ function isGatewayReady(expectedPid) {
27634
28406
  database?.close();
27635
28407
  }
27636
28408
  }
27637
- function readyTimeoutMs() {
27638
- const value = Number(process.env.SUPERTASK_GATEWAY_READY_TIMEOUT_MS ?? 3e4);
28409
+ function gatewayVersionFromLock(expectedPid, path) {
28410
+ if (!existsSync4(path)) return void 0;
28411
+ let database = null;
28412
+ try {
28413
+ database = new Database4(path, { readonly: true });
28414
+ const row = database.query(
28415
+ "SELECT pid, version FROM gateway_lock WHERE id = 1"
28416
+ ).get();
28417
+ if (!row || row.pid !== expectedPid) return void 0;
28418
+ return row.version;
28419
+ } catch {
28420
+ return void 0;
28421
+ } finally {
28422
+ database?.close();
28423
+ }
28424
+ }
28425
+ function readyTimeoutMs(env = process.env) {
28426
+ const value = Number(env.SUPERTASK_GATEWAY_READY_TIMEOUT_MS ?? 3e4);
27639
28427
  return Number.isFinite(value) && value > 0 ? value : 3e4;
27640
28428
  }
27641
- function waitForGatewayReady(pid) {
27642
- const deadline = Date.now() + readyTimeoutMs();
28429
+ function waitForGatewayReady(pid, path = databasePath(), env = process.env) {
28430
+ const deadline = Date.now() + readyTimeoutMs(env);
27643
28431
  const sleeper = new Int32Array(new SharedArrayBuffer(4));
27644
28432
  while (Date.now() < deadline) {
27645
- if (isGatewayReady(pid)) return true;
28433
+ if (isGatewayReady(pid, path)) return true;
27646
28434
  Atomics.wait(sleeper, 0, 0, 100);
27647
28435
  }
27648
- return isGatewayReady(pid);
28436
+ return isGatewayReady(pid, path);
27649
28437
  }
27650
28438
  function findBunPath() {
27651
28439
  const override = process.env.SUPERTASK_BUN_BIN;
@@ -27657,12 +28445,64 @@ function findBunPath() {
27657
28445
  return process.execPath;
27658
28446
  }
27659
28447
  }
27660
- function pm2StartGateway(gatewayEntry = resolveGatewayEntry()) {
27661
- const configuredKillTimeout = Number(process.env.SUPERTASK_PM2_KILL_TIMEOUT_MS);
27662
- const killTimeoutMs = Number.isInteger(configuredKillTimeout) && configuredKillTimeout >= 5e3 ? configuredKillTimeout : loadConfig().worker.shutdownGracePeriodMs + 5e3;
28448
+ function pm2MaxMemoryRestart(env = process.env) {
28449
+ const value = env.SUPERTASK_PM2_MAX_MEMORY ?? "512M";
28450
+ if (!/^\d+(?:K|M|G)$/i.test(value)) {
28451
+ throw new Error("[supertask] SUPERTASK_PM2_MAX_MEMORY \u5FC5\u987B\u4F7F\u7528 512M / 1G \u8FD9\u7C7B\u683C\u5F0F");
28452
+ }
28453
+ return value;
28454
+ }
28455
+ function gatewayKillTimeoutMs(runtime) {
28456
+ const shutdownGracePeriodMs = loadConfig(runtimeScope(runtime).configPath).worker.shutdownGracePeriodMs;
28457
+ const minimumKillTimeoutMs = shutdownGracePeriodMs + 15e3;
28458
+ const configuredValue = runtime.env.SUPERTASK_PM2_KILL_TIMEOUT_MS;
28459
+ if (configuredValue !== void 0) {
28460
+ const configuredKillTimeout = Number(configuredValue);
28461
+ if (!Number.isInteger(configuredKillTimeout) || configuredKillTimeout < minimumKillTimeoutMs) {
28462
+ throw new Error(
28463
+ `[supertask] SUPERTASK_PM2_KILL_TIMEOUT_MS \u5FC5\u987B\u662F\u81F3\u5C11 ${minimumKillTimeoutMs} \u7684\u6574\u6570\uFF08worker shutdown grace ${shutdownGracePeriodMs}ms + 15000ms \u6536\u5C3E\u4F59\u91CF\uFF09`
28464
+ );
28465
+ }
28466
+ return configuredKillTimeout;
28467
+ }
28468
+ return Math.max(runtime.killTimeoutMs ?? 0, minimumKillTimeoutMs);
28469
+ }
28470
+ function gatewayTerminationCommandTimeoutMs(runtime) {
28471
+ const effectiveKillTimeoutMs = Math.max(
28472
+ runtime.killTimeoutMs ?? 0,
28473
+ gatewayKillTimeoutMs(runtime)
28474
+ );
28475
+ const minimumCommandTimeoutMs = effectiveKillTimeoutMs + 5e3;
28476
+ const configuredValue = runtime.env.SUPERTASK_PM2_COMMAND_TIMEOUT_MS;
28477
+ if (configuredValue !== void 0) {
28478
+ const configuredTimeoutMs = Number(configuredValue);
28479
+ if (!Number.isInteger(configuredTimeoutMs) || configuredTimeoutMs < minimumCommandTimeoutMs) {
28480
+ throw new Error(
28481
+ `[supertask] SUPERTASK_PM2_COMMAND_TIMEOUT_MS \u5FC5\u987B\u662F\u81F3\u5C11 ${minimumCommandTimeoutMs} \u7684\u6574\u6570\uFF08Gateway kill timeout ${effectiveKillTimeoutMs}ms + 5000ms PM2 \u6536\u5C3E\u4F59\u91CF\uFF09`
28482
+ );
28483
+ }
28484
+ return configuredTimeoutMs;
28485
+ }
28486
+ return Math.max(pm2CommandTimeoutMs(runtime.env), minimumCommandTimeoutMs);
28487
+ }
28488
+ function requirePm2Termination(command, action, runtime) {
28489
+ return requirePm2([command, PROCESS_NAME], action, {
28490
+ env: runtime.env,
28491
+ timeoutMs: gatewayTerminationCommandTimeoutMs(runtime)
28492
+ });
28493
+ }
28494
+ function pm2StartGateway(runtime = currentGatewayRuntime()) {
28495
+ try {
28496
+ accessSync(runtime.bunPath, constants.X_OK);
28497
+ accessSync(runtime.gatewayEntry, constants.R_OK);
28498
+ if (!statSync(runtime.cwd).isDirectory()) throw new Error("cwd is not a directory");
28499
+ } catch (error45) {
28500
+ throw new Error(`[supertask] Gateway \u76EE\u6807\u8FD0\u884C\u65F6\u4E0D\u53EF\u7528: ${error45 instanceof Error ? error45.message : String(error45)}`);
28501
+ }
28502
+ const killTimeoutMs = gatewayKillTimeoutMs(runtime);
27663
28503
  requirePm2([
27664
28504
  "start",
27665
- findBunPath(),
28505
+ runtime.bunPath,
27666
28506
  "--name",
27667
28507
  PROCESS_NAME,
27668
28508
  "--interpreter",
@@ -27671,43 +28511,186 @@ function pm2StartGateway(gatewayEntry = resolveGatewayEntry()) {
27671
28511
  "5000",
27672
28512
  "--max-restarts",
27673
28513
  "30",
28514
+ "--max-memory-restart",
28515
+ pm2MaxMemoryRestart(runtime.env),
27674
28516
  "--kill-timeout",
27675
28517
  String(killTimeoutMs),
28518
+ "--cwd",
28519
+ runtime.cwd,
27676
28520
  "--",
27677
- gatewayEntry
27678
- ], "pm2 start");
27679
- const started = pm2JsonList().find((item) => item.name === PROCESS_NAME);
28521
+ runtime.gatewayEntry
28522
+ ], "pm2 start", { env: runtime.env });
28523
+ const started = pm2JsonList(runtime.env).find((item) => item.name === PROCESS_NAME);
27680
28524
  if (started?.pm2_env?.status !== "online") {
27681
28525
  throw new Error(`[supertask] Gateway did not become online (status: ${started?.pm2_env?.status ?? "missing"})`);
27682
28526
  }
27683
- if (typeof started.pid !== "number" || !waitForGatewayReady(started.pid)) {
28527
+ const observedRuntime = gatewayRuntimeFromProcess(started);
28528
+ if (!observedRuntime || observedRuntime.gatewayEntry !== resolve(runtime.gatewayEntry) || observedRuntime.bunPath !== runtime.bunPath || observedRuntime.cwd !== resolve(runtime.cwd) || !scopesMatch(runtimeScope(observedRuntime), runtimeScope(runtime))) {
28529
+ throw new Error("[supertask] PM2 online \u8BB0\u5F55\u4E0E\u672C\u6B21\u542F\u52A8\u76EE\u6807\u4E0D\u4E00\u81F4\uFF0C\u5DF2\u62D2\u7EDD\u63A5\u7BA1");
28530
+ }
28531
+ if (typeof started.pid !== "number" || !waitForGatewayReady(started.pid, runtimeScope(runtime).databasePath, runtime.env)) {
27684
28532
  throw new Error("[supertask] Gateway \u8FDB\u7A0B online\uFF0C\u4F46\u672A\u5728\u9650\u5B9A\u65F6\u95F4\u5185\u5C31\u7EEA\uFF1B\u8BF7\u67E5\u770B pm2 logs supertask-gateway");
27685
28533
  }
27686
28534
  }
27687
- function savePm2State() {
27688
- requirePm2(["save"], "pm2 save");
28535
+ function assertRuntimeCanControlPm2(runtime, existing) {
28536
+ if (!existing) return;
28537
+ if (!runtime) {
28538
+ throw new Error(
28539
+ "[supertask] \u65E0\u6CD5\u5B8C\u6574\u9A8C\u8BC1\u5DF2\u6709 Gateway \u7684\u5165\u53E3\u3001Bun \u548C cwd\uFF0C\u5DF2\u62D2\u7EDD\u5220\u9664\u73B0\u6709\u8FDB\u7A0B"
28540
+ );
28541
+ }
28542
+ const probe = pm2Exec(["--version"], { env: runtime.env });
28543
+ if (!probe.ok) {
28544
+ throw new Error(
28545
+ `[supertask] \u4FDD\u5B58\u7684 Gateway \u8FD0\u884C\u73AF\u5883\u65E0\u6CD5\u6267\u884C pm2\uFF0C\u5DF2\u62D2\u7EDD\u5220\u9664\u73B0\u6709\u8FDB\u7A0B\uFF1B\u8BF7\u5148\u4FEE\u590D PATH \u6216 SUPERTASK_PM2_BIN: ${probe.output || "pm2 unavailable"}`
28546
+ );
28547
+ }
28548
+ const observed = pm2JsonList(runtime.env).find((item) => item.name === PROCESS_NAME);
28549
+ const observedRuntime = gatewayRuntimeFromProcess(observed);
28550
+ if (!observed || observed.pid !== existing.pid || !observedRuntime || observedRuntime.gatewayEntry !== runtime.gatewayEntry || observedRuntime.bunPath !== runtime.bunPath || observedRuntime.cwd !== runtime.cwd) {
28551
+ throw new Error("[supertask] \u4FDD\u5B58\u7684 PM2 \u73AF\u5883\u65E0\u6CD5\u91CD\u73B0\u540C\u4E00 Gateway \u8BB0\u5F55\uFF0C\u5DF2\u62D2\u7EDD\u5220\u9664\u73B0\u6709\u8FDB\u7A0B");
28552
+ }
28553
+ }
28554
+ function savePm2State(env = process.env) {
28555
+ requirePm2(["save"], "pm2 save", { env });
28556
+ }
28557
+ function canonicalManagementLockPath(env = process.env, cwd = process.cwd()) {
28558
+ const home = runtimeHome(env);
28559
+ const pm2Home = env.PM2_HOME ? runtimePath(env.PM2_HOME, cwd) : join4(home, ".pm2");
28560
+ return join4(pm2Home, "supertask-gateway.manage.sqlite");
28561
+ }
28562
+ function addLegacyManagementLock(paths, env, cwd) {
28563
+ const value = env?.SUPERTASK_PM2_MANAGEMENT_LOCK;
28564
+ if (typeof value === "string" && value.length > 0) paths.add(runtimePath(value, cwd));
28565
+ }
28566
+ function savedManagementLockPaths() {
28567
+ const canonical = canonicalManagementLockPath();
28568
+ const legacy = /* @__PURE__ */ new Set();
28569
+ addLegacyManagementLock(legacy, process.env, process.cwd());
28570
+ const pm2Home = currentScope().pm2Home;
28571
+ try {
28572
+ const dump = JSON.parse(readFileSync3(join4(pm2Home, "dump.pm2"), "utf8"));
28573
+ if (Array.isArray(dump)) {
28574
+ const gateway = dump.find((item) => item.name === PROCESS_NAME);
28575
+ const cwd = gateway?.pm2_env?.pm_cwd;
28576
+ if (typeof cwd === "string") {
28577
+ addLegacyManagementLock(
28578
+ legacy,
28579
+ gateway?.pm2_env?.env ?? gateway?.env,
28580
+ cwd
28581
+ );
28582
+ }
28583
+ }
28584
+ } catch {
28585
+ }
28586
+ if (process.platform === "darwin") {
28587
+ try {
28588
+ const configured = plistValue(
28589
+ readFileSync3(launchAgentPath(), "utf8"),
28590
+ "SUPERTASK_PM2_MANAGEMENT_LOCK"
28591
+ );
28592
+ if (configured) legacy.add(resolve(configured));
28593
+ } catch {
28594
+ }
28595
+ }
28596
+ if (isPm2Installed()) {
28597
+ const gateway = pm2JsonList().find((item) => item.name === PROCESS_NAME);
28598
+ const cwd = gateway?.pm2_env?.pm_cwd;
28599
+ if (typeof cwd === "string") {
28600
+ addLegacyManagementLock(
28601
+ legacy,
28602
+ gateway?.pm2_env?.env ?? gateway?.env,
28603
+ cwd
28604
+ );
28605
+ }
28606
+ }
28607
+ return [
28608
+ canonical,
28609
+ ...[...legacy].filter((path) => path !== canonical).sort()
28610
+ ];
28611
+ }
28612
+ function withManagementLocks(paths, timeoutMs, action, index2 = 0) {
28613
+ const path = paths[index2];
28614
+ if (!path) return action();
28615
+ return withExclusiveManagementLock(
28616
+ path,
28617
+ timeoutMs,
28618
+ () => withManagementLocks(paths, timeoutMs, action, index2 + 1)
28619
+ );
28620
+ }
28621
+ function assertMacLaunchAgentPm2HomeMatchesCurrent() {
28622
+ if (process.platform !== "darwin") return;
28623
+ try {
28624
+ const configuredPm2Home = plistValue(
28625
+ readFileSync3(launchAgentPath(), "utf8"),
28626
+ "PM2_HOME"
28627
+ );
28628
+ if (!configuredPm2Home) return;
28629
+ const installedPm2Home = resolve(configuredPm2Home);
28630
+ const activePm2Home = currentScope().pm2Home;
28631
+ if (installedPm2Home !== activePm2Home) {
28632
+ throw new Error(
28633
+ `[supertask] \u5DF2\u5B89\u88C5\u7684 macOS LaunchAgent \u4F7F\u7528 PM2_HOME=${installedPm2Home}\uFF0C\u5F53\u524D CLI \u4F7F\u7528 ${activePm2Home}\uFF1B\u4E3A\u907F\u514D\u4E24\u4E2A PM2 daemon \u540C\u65F6\u7BA1\u7406 Gateway\uFF0C\u5DF2\u62D2\u7EDD\u4FEE\u6539\u3002\u8BF7\u4F7F\u7528 PM2_HOME=${installedPm2Home} \u91CD\u65B0\u6267\u884C\u547D\u4EE4`
28634
+ );
28635
+ }
28636
+ } catch (error45) {
28637
+ if (error45 instanceof Error && error45.message.startsWith("[supertask] \u5DF2\u5B89\u88C5\u7684 macOS LaunchAgent")) {
28638
+ throw error45;
28639
+ }
28640
+ if (error45.code !== "ENOENT") throw error45;
28641
+ }
28642
+ }
28643
+ function withManagementLock(action) {
28644
+ const paths = savedManagementLockPaths();
28645
+ const configuredTimeout = Number(process.env.SUPERTASK_PM2_MANAGEMENT_LOCK_TIMEOUT_MS ?? 15e3);
28646
+ const timeoutMs = Number.isFinite(configuredTimeout) && configuredTimeout > 0 ? configuredTimeout : 15e3;
28647
+ try {
28648
+ return withManagementLocks(paths, timeoutMs, () => {
28649
+ assertMacLaunchAgentPm2HomeMatchesCurrent();
28650
+ return action();
28651
+ });
28652
+ } catch (error45) {
28653
+ if (error45 instanceof ManagementLockBusyError) {
28654
+ throw new Error("[supertask] \u53E6\u4E00\u4E2A Gateway \u7BA1\u7406\u64CD\u4F5C\u4ECD\u5728\u8FDB\u884C\uFF0C\u5DF2\u62D2\u7EDD\u5E76\u53D1\u4FEE\u6539 PM2 \u72B6\u6001");
28655
+ }
28656
+ throw error45;
28657
+ }
27689
28658
  }
27690
28659
  function upgrade(target) {
28660
+ return withManagementLock(() => upgradeUnlocked(target));
28661
+ }
28662
+ function upgradeUnlocked(target) {
27691
28663
  if (!isPm2Installed()) {
27692
28664
  throw new Error("[supertask] pm2 is not installed. Run `supertask install` first.");
27693
28665
  }
27694
- const before = getRunningVersion();
27695
- const oldGatewayEntry = resolveGatewayEntry();
27696
- const currentVersion = target?.version ?? getPackageVersion();
27697
28666
  const existing = pm2JsonList().find((item) => item.name === PROCESS_NAME);
27698
- if (existing) requirePm2(["delete", PROCESS_NAME], "pm2 delete old Gateway");
28667
+ const oldRuntime = gatewayRuntimeFromProcess(existing) ?? (process.env.SUPERTASK_GATEWAY_ENTRY ? currentGatewayRuntime(resolveGatewayEntry()) : null);
28668
+ const before = getRunningVersion(oldRuntime?.env ?? process.env, oldRuntime?.cwd);
28669
+ const currentVersion = target?.version ?? getPackageVersion();
28670
+ const targetRuntime = oldRuntime ? { ...oldRuntime, gatewayEntry: resolve(target?.gatewayEntry ?? oldRuntime.gatewayEntry) } : currentGatewayRuntime(target?.gatewayEntry ?? resolveGatewayEntry());
28671
+ assertRuntimeCanControlPm2(oldRuntime, existing);
28672
+ gatewayKillTimeoutMs(targetRuntime);
28673
+ gatewayTerminationCommandTimeoutMs(targetRuntime);
28674
+ if (existing) requirePm2Termination("delete", "pm2 delete old Gateway", oldRuntime);
27699
28675
  try {
27700
- pm2StartGateway(target?.gatewayEntry ?? oldGatewayEntry);
27701
- writeRunningVersion(currentVersion);
27702
- savePm2State();
28676
+ pm2StartGateway(targetRuntime);
28677
+ savePm2State(targetRuntime.env);
28678
+ writeRunningVersion(currentVersion, targetRuntime.env, targetRuntime.cwd);
27703
28679
  return { before, after: currentVersion, restarted: true };
27704
28680
  } catch (error45) {
27705
- const failed = pm2JsonList().find((item) => item.name === PROCESS_NAME);
27706
- if (failed) requirePm2(["delete", PROCESS_NAME], "pm2 delete failed Gateway");
28681
+ const failed = pm2JsonList(targetRuntime.env).find((item) => item.name === PROCESS_NAME);
28682
+ if (failed) {
28683
+ requirePm2Termination(
28684
+ "delete",
28685
+ "pm2 delete failed Gateway",
28686
+ gatewayRuntimeFromProcess(failed) ?? targetRuntime
28687
+ );
28688
+ }
28689
+ if (!oldRuntime) throw error45;
27707
28690
  try {
27708
- pm2StartGateway(oldGatewayEntry);
27709
- if (before) writeRunningVersion(before);
27710
- savePm2State();
28691
+ pm2StartGateway(oldRuntime);
28692
+ savePm2State(oldRuntime.env);
28693
+ if (before) writeRunningVersion(before, oldRuntime.env, oldRuntime.cwd);
27711
28694
  } catch (rollbackError) {
27712
28695
  const original = error45 instanceof Error ? error45.message : String(error45);
27713
28696
  const rollback = rollbackError instanceof Error ? rollbackError.message : String(rollbackError);
@@ -27718,50 +28701,84 @@ function upgrade(target) {
27718
28701
  }
27719
28702
  }
27720
28703
  function ensureGateway() {
28704
+ return withManagementLock(ensureGatewayUnlocked);
28705
+ }
28706
+ function ensureGatewayUnlocked() {
27721
28707
  if (!isPm2Installed()) {
27722
28708
  return { ok: false, reason: "pm2-not-installed" };
27723
28709
  }
27724
28710
  const currentVersion = getPackageVersion();
27725
28711
  const processList = pm2JsonList();
27726
28712
  const existing = processList.find((item) => item.name === PROCESS_NAME);
27727
- if (existing?.pm2_env?.status === "online" && typeof existing.pid === "number" && isGatewayReady(existing.pid) && getRunningVersion() === currentVersion) {
28713
+ const oldRuntime = gatewayRuntimeFromProcess(existing);
28714
+ if (oldRuntime && !scopesMatch(currentScope(), runtimeScope(oldRuntime))) {
28715
+ throw new Error("[supertask] \u5F53\u524D OpenCode/CLI \u4E0E PM2 Gateway \u7684\u6570\u636E\u5E93\u3001\u914D\u7F6E\u6216 OpenCode \u53EF\u6267\u884C\u6587\u4EF6\u4F5C\u7528\u57DF\u4E0D\u4E00\u81F4\uFF0C\u5DF2\u62D2\u7EDD\u7EE7\u7EED\u5199\u5165");
28716
+ }
28717
+ assertRuntimeCanControlPm2(oldRuntime, existing);
28718
+ const lockedVersion = existing && typeof existing.pid === "number" && oldRuntime ? gatewayVersionFromLock(existing.pid, runtimeScope(oldRuntime).databasePath) : void 0;
28719
+ if (existing?.pm2_env?.status === "online" && typeof existing.pid === "number" && oldRuntime !== null && isGatewayReady(existing.pid, runtimeScope(oldRuntime).databasePath) && (lockedVersion === currentVersion || lockedVersion === void 0 && getRunningVersion(oldRuntime.env, oldRuntime.cwd) === currentVersion)) {
27728
28720
  return { ok: true, action: "already-running" };
27729
28721
  }
27730
- if (existing) requirePm2(["delete", PROCESS_NAME], "pm2 delete stale Gateway");
27731
- pm2StartGateway();
27732
- writeRunningVersion(currentVersion);
27733
- savePm2State();
28722
+ const targetRuntime = oldRuntime ? { ...oldRuntime, gatewayEntry: resolve(resolveGatewayEntry()) } : currentGatewayRuntime();
28723
+ const before = getRunningVersion(oldRuntime?.env ?? process.env, oldRuntime?.cwd);
28724
+ gatewayKillTimeoutMs(targetRuntime);
28725
+ gatewayTerminationCommandTimeoutMs(targetRuntime);
28726
+ if (existing) requirePm2Termination("delete", "pm2 delete stale Gateway", oldRuntime);
28727
+ try {
28728
+ pm2StartGateway(targetRuntime);
28729
+ savePm2State(targetRuntime.env);
28730
+ writeRunningVersion(currentVersion, targetRuntime.env, targetRuntime.cwd);
28731
+ } catch (error45) {
28732
+ const failed = pm2JsonList(targetRuntime.env).find((item) => item.name === PROCESS_NAME);
28733
+ if (failed) {
28734
+ requirePm2Termination(
28735
+ "delete",
28736
+ "pm2 delete failed Gateway",
28737
+ gatewayRuntimeFromProcess(failed) ?? targetRuntime
28738
+ );
28739
+ }
28740
+ if (oldRuntime) {
28741
+ try {
28742
+ pm2StartGateway(oldRuntime);
28743
+ savePm2State(oldRuntime.env);
28744
+ if (before) writeRunningVersion(before, oldRuntime.env, oldRuntime.cwd);
28745
+ } catch (rollbackError) {
28746
+ const original = error45 instanceof Error ? error45.message : String(error45);
28747
+ const rollback = rollbackError instanceof Error ? rollbackError.message : String(rollbackError);
28748
+ throw new Error(`${original}; \u65E7 Gateway \u56DE\u6EDA\u4E5F\u5931\u8D25: ${rollback}`);
28749
+ }
28750
+ const message = error45 instanceof Error ? error45.message : String(error45);
28751
+ throw new Error(`${message}; \u5DF2\u56DE\u6EDA\u5230\u65E7 Gateway`);
28752
+ }
28753
+ throw error45;
28754
+ }
27734
28755
  return { ok: true, action: existing ? "restarted" : "started" };
27735
28756
  }
27736
28757
 
27737
28758
  // src/daemon/update.ts
27738
28759
  import { spawnSync as spawnSync2 } from "child_process";
27739
- import { existsSync as existsSync4, readFileSync as readFileSync3, readdirSync } from "fs";
28760
+ import { existsSync as existsSync5, readFileSync as readFileSync4, readdirSync } from "fs";
27740
28761
  import { homedir as homedir4 } from "os";
27741
- import { join as join4 } from "path";
28762
+ import { join as join5 } from "path";
27742
28763
  var PACKAGE_NAME = "opencode-supertask";
27743
28764
  function pluginAt(packageDir) {
27744
- const packageJson = join4(packageDir, "package.json");
27745
- const gatewayEntry = join4(packageDir, "dist/gateway/index.js");
27746
- if (!existsSync4(packageJson) || !existsSync4(gatewayEntry)) return null;
28765
+ const packageJson = join5(packageDir, "package.json");
28766
+ const gatewayEntry = join5(packageDir, "dist/gateway/index.js");
28767
+ if (!existsSync5(packageJson) || !existsSync5(gatewayEntry)) return null;
27747
28768
  try {
27748
- const pkg = JSON.parse(readFileSync3(packageJson, "utf8"));
28769
+ const pkg = JSON.parse(readFileSync4(packageJson, "utf8"));
27749
28770
  if (pkg.name !== PACKAGE_NAME || typeof pkg.version !== "string") return null;
27750
28771
  return { packageDir, gatewayEntry, version: pkg.version };
27751
28772
  } catch {
27752
28773
  return null;
27753
28774
  }
27754
28775
  }
27755
- function versionParts(version3) {
27756
- const match = /^(\d+)\.(\d+)\.(\d+)(?:[-+].*)?$/.exec(version3);
27757
- return match ? match.slice(1).map(Number) : null;
27758
- }
27759
28776
  function cacheRoot() {
27760
- return process.env.SUPERTASK_OPENCODE_CACHE_DIR ?? join4(homedir4(), ".cache/opencode/packages");
28777
+ return process.env.SUPERTASK_OPENCODE_CACHE_DIR ?? join5(homedir4(), ".cache/opencode/packages");
27761
28778
  }
27762
28779
  function installedPlugins() {
27763
28780
  const root = cacheRoot();
27764
- const packageDirs = existsSync4(root) ? readdirSync(root, { withFileTypes: true }).filter((entry) => entry.isDirectory() && (entry.name === PACKAGE_NAME || entry.name.startsWith(`${PACKAGE_NAME}@`))).map((entry) => join4(root, entry.name, "node_modules", PACKAGE_NAME)) : [];
28781
+ const packageDirs = existsSync5(root) ? readdirSync(root, { withFileTypes: true }).filter((entry) => entry.isDirectory() && (entry.name === PACKAGE_NAME || entry.name.startsWith(`${PACKAGE_NAME}@`))).map((entry) => join5(root, entry.name, "node_modules", PACKAGE_NAME)) : [];
27765
28782
  return packageDirs.map(pluginAt).filter((plugin) => plugin !== null);
27766
28783
  }
27767
28784
  function opencodeBin() {
@@ -27795,7 +28812,7 @@ function latestVersion() {
27795
28812
  } catch {
27796
28813
  throw new Error(`[supertask] npm latest \u8FD4\u56DE\u65E0\u6CD5\u89E3\u6790: ${output || "(empty)"}`);
27797
28814
  }
27798
- if (typeof version3 !== "string" || versionParts(version3) === null) {
28815
+ if (typeof version3 !== "string" || !isSemanticVersion(version3)) {
27799
28816
  throw new Error(`[supertask] npm latest \u7248\u672C\u65E0\u6548: ${String(version3)}`);
27800
28817
  }
27801
28818
  return version3;
@@ -27808,8 +28825,10 @@ function resolveInstalledVersion(expectedVersion) {
27808
28825
  const actual = installed.length > 0 ? installed.map((plugin) => plugin.version).join(", ") : "\u672A\u627E\u5230\u53EF\u8FD0\u884C\u7F13\u5B58";
27809
28826
  throw new Error(`[supertask] OpenCode \u63D2\u4EF6\u7F13\u5B58\u7248\u672C\u4E0D\u5339\u914D\uFF1A\u671F\u671B ${expectedVersion}\uFF0C\u5B9E\u9645 ${actual}`);
27810
28827
  }
27811
- function installLatestPlugin() {
27812
- const version3 = latestVersion();
28828
+ function installPluginVersion(version3) {
28829
+ if (!isSemanticVersion(version3)) {
28830
+ throw new Error(`[supertask] OpenCode \u63D2\u4EF6\u7248\u672C\u65E0\u6548: ${version3}`);
28831
+ }
27813
28832
  const result = spawnSync2(opencodeBin(), [
27814
28833
  "plugin",
27815
28834
  `${PACKAGE_NAME}@${version3}`,
@@ -27829,9 +28848,27 @@ function installLatestPlugin() {
27829
28848
  }
27830
28849
  return resolveInstalledVersion(version3);
27831
28850
  }
28851
+ function installLatestPlugin() {
28852
+ return installPluginVersion(latestVersion());
28853
+ }
27832
28854
 
27833
28855
  // plugin/supertask.ts
27834
28856
  var _initialized = false;
28857
+ var _writeBlockedReason = null;
28858
+ function readyGatewayVersion() {
28859
+ const lockRow = sqlite.prepare(
28860
+ "SELECT heartbeat_at, ready_at, version FROM gateway_lock WHERE id = 1"
28861
+ ).get();
28862
+ return {
28863
+ fresh: lockRow?.ready_at != null && Date.now() - lockRow.heartbeat_at < 3e4,
28864
+ version: lockRow?.version ?? null
28865
+ };
28866
+ }
28867
+ function shouldAttemptGatewayReplacement(pluginVersion, gatewayVersion) {
28868
+ if (gatewayVersion === null) return true;
28869
+ const direction = compareSemanticVersions(pluginVersion, gatewayVersion);
28870
+ return direction !== null && direction >= 0;
28871
+ }
27835
28872
  function ensureInit() {
27836
28873
  if (_initialized) return;
27837
28874
  try {
@@ -27840,9 +28877,16 @@ function ensureInit() {
27840
28877
  console.error("[supertask] DB init failed:", err instanceof Error ? err.message : String(err));
27841
28878
  return;
27842
28879
  }
28880
+ const expectedVersion = getPackageVersion();
28881
+ let initialReady = { fresh: false, version: null };
27843
28882
  try {
27844
- const lockRow = sqlite.prepare("SELECT pid, heartbeat_at, ready_at FROM gateway_lock WHERE id = 1").get();
27845
- if (lockRow?.ready_at != null && Date.now() - lockRow.heartbeat_at < 3e4) {
28883
+ initialReady = readyGatewayVersion();
28884
+ if (initialReady.fresh && initialReady.version === expectedVersion) {
28885
+ _initialized = true;
28886
+ return;
28887
+ }
28888
+ if (initialReady.fresh && !shouldAttemptGatewayReplacement(expectedVersion, initialReady.version)) {
28889
+ _writeBlockedReason = `Gateway \u7248\u672C ${initialReady.version ?? "unknown"} \u4E0E\u5F53\u524D\u63D2\u4EF6 ${expectedVersion} \u4E0D\u4E00\u81F4\uFF1B\u8BF7\u91CD\u542F OpenCode \u6216\u663E\u5F0F\u6267\u884C supertask upgrade`;
27846
28890
  _initialized = true;
27847
28891
  return;
27848
28892
  }
@@ -27851,13 +28895,29 @@ function ensureInit() {
27851
28895
  try {
27852
28896
  const gateway = ensureGateway();
27853
28897
  if (!gateway.ok) {
27854
- console.warn("[supertask] Gateway \u672A\u81EA\u52A8\u542F\u52A8\uFF1A\u672A\u5B89\u88C5 pm2\u3002\u8FD0\u884C `supertask install` \u542F\u7528\u5E38\u9A7B\u6267\u884C\uFF0C\u6216\u8FD0\u884C `supertask gateway` \u524D\u53F0\u542F\u52A8\u3002");
28898
+ if (initialReady.fresh) {
28899
+ _writeBlockedReason = `Gateway \u7248\u672C ${initialReady.version ?? "unknown"} \u4E0E\u63D2\u4EF6 ${expectedVersion} \u4E0D\u4E00\u81F4\uFF0C\u4E14\u672A\u5B89\u88C5 pm2 \u65E0\u6CD5\u5B89\u5168\u66FF\u6362`;
28900
+ } else {
28901
+ console.warn("[supertask] Gateway \u672A\u81EA\u52A8\u542F\u52A8\uFF1A\u672A\u5B89\u88C5 pm2\u3002\u8FD0\u884C `supertask install` \u542F\u7528\u5E38\u9A7B\u6267\u884C\uFF0C\u6216\u8FD0\u884C `supertask gateway` \u524D\u53F0\u542F\u52A8\u3002");
28902
+ }
28903
+ } else {
28904
+ const current = readyGatewayVersion();
28905
+ if (!current.fresh || current.version !== expectedVersion) {
28906
+ _writeBlockedReason = `Gateway \u5C31\u7EEA\u7248\u672C ${current.version ?? "unknown"} \u4E0E\u63D2\u4EF6 ${expectedVersion} \u4E0D\u4E00\u81F4`;
28907
+ }
27855
28908
  }
27856
28909
  } catch (error45) {
27857
- console.error("[supertask] Gateway init failed:", error45 instanceof Error ? error45.message : String(error45));
28910
+ _writeBlockedReason = error45 instanceof Error ? error45.message : String(error45);
28911
+ console.error("[supertask] Gateway init failed:", _writeBlockedReason);
27858
28912
  }
27859
28913
  _initialized = true;
27860
28914
  }
28915
+ function assertRuntimeWritable() {
28916
+ ensureInit();
28917
+ if (_writeBlockedReason) {
28918
+ throw new Error(`[supertask] \u5DF2\u963B\u6B62\u961F\u5217\u5199\u5165: ${_writeBlockedReason}`);
28919
+ }
28920
+ }
27861
28921
  var SYSTEM_INSTRUCTION = `
27862
28922
  ## SuperTask \u4EFB\u52A1\u961F\u5217\u7CFB\u7EDF
27863
28923
 
@@ -27895,25 +28955,25 @@ var SuperTaskPlugin = async () => {
27895
28955
  supertask_add: tool({
27896
28956
  description: "\u521B\u5EFA\u65B0\u4EFB\u52A1\u5230\u961F\u5217\u3002\u8FD4\u56DE\u4EFB\u52A1 ID\u3002\u4EFB\u52A1\u6309 urgency\u3001importance\u3001createdAt\u3001id \u7684\u987A\u5E8F\u8C03\u5EA6\u3002",
27897
28957
  args: {
27898
- name: tool.schema.string().describe("\u4EFB\u52A1\u540D\u79F0\uFF08\u4EBA\u7C7B\u53EF\u8BFB\uFF09"),
27899
- agent: tool.schema.string().describe("\u6267\u884C\u7684 Agent \u540D\u79F0\uFF0C\u5982 localize-gen, course-gen"),
27900
- prompt: tool.schema.string().describe("\u53D1\u9001\u7ED9 Agent \u7684\u5B8C\u6574\u63D0\u793A\u8BCD"),
28958
+ name: tool.schema.string().trim().min(1).describe("\u4EFB\u52A1\u540D\u79F0\uFF08\u4EBA\u7C7B\u53EF\u8BFB\uFF09"),
28959
+ agent: tool.schema.string().trim().min(1).describe("\u6267\u884C\u7684 Agent \u540D\u79F0\uFF0C\u5982 localize-gen, course-gen"),
28960
+ prompt: tool.schema.string().trim().min(1).describe("\u53D1\u9001\u7ED9 Agent \u7684\u5B8C\u6574\u63D0\u793A\u8BCD"),
27901
28961
  model: tool.schema.string().optional().describe("\u4F7F\u7528\u7684\u6A21\u578B\uFF0C\u5982 gemini-2.5-pro"),
27902
- category: tool.schema.string().optional().describe("\u4EFB\u52A1\u5206\u7C7B\uFF1Atranslate/generate/review/test/general"),
27903
- importance: tool.schema.number().optional().describe("\u91CD\u8981\u7A0B\u5EA6 1-5\uFF085 \u6700\u91CD\u8981\uFF09"),
27904
- urgency: tool.schema.number().optional().describe("\u7D27\u6025\u7A0B\u5EA6 1-5\uFF085 \u6700\u7D27\u6025\uFF09"),
28962
+ category: tool.schema.enum(["translate", "generate", "review", "test", "general"]).optional().describe("\u4EFB\u52A1\u5206\u7C7B"),
28963
+ importance: tool.schema.number().int().min(1).max(5).optional().describe("\u91CD\u8981\u7A0B\u5EA6 1-5\uFF085 \u6700\u91CD\u8981\uFF09"),
28964
+ urgency: tool.schema.number().int().min(1).max(5).optional().describe("\u7D27\u6025\u7A0B\u5EA6 1-5\uFF085 \u6700\u7D27\u6025\uFF09"),
27905
28965
  batchId: tool.schema.string().optional().describe("\u6279\u6B21 ID\uFF0C\u7528\u4E8E\u5206\u7EC4\u7BA1\u7406"),
27906
- dependsOn: tool.schema.number().optional().describe("\u4F9D\u8D56\u7684\u4EFB\u52A1 ID\uFF0C\u8BE5\u4EFB\u52A1\u5B8C\u6210\u540E\u624D\u4F1A\u6267\u884C"),
27907
- max_retries: tool.schema.number().optional().describe("\u9996\u6B21\u6267\u884C\u4E4B\u5916\u5141\u8BB8\u7684\u91CD\u8BD5\u6B21\u6570\uFF0C\u9ED8\u8BA4 3"),
27908
- retry_backoff_ms: tool.schema.number().optional().describe("\u91CD\u8BD5\u9000\u907F\u57FA\u7840\u95F4\u9694 ms\uFF0C\u9ED8\u8BA4 30000"),
27909
- timeout_ms: tool.schema.number().optional().describe("\u4EFB\u52A1\u786C\u8D85\u65F6 ms\uFF1B\u672A\u4F20\u5219\u4F7F\u7528 Gateway \u9ED8\u8BA4\u503C"),
28966
+ dependsOn: tool.schema.number().int().positive().optional().describe("\u4F9D\u8D56\u7684\u4EFB\u52A1 ID\uFF0C\u8BE5\u4EFB\u52A1\u5B8C\u6210\u540E\u624D\u4F1A\u6267\u884C"),
28967
+ max_retries: tool.schema.number().int().min(0).max(1e3).optional().describe("\u9996\u6B21\u6267\u884C\u4E4B\u5916\u5141\u8BB8\u7684\u91CD\u8BD5\u6B21\u6570\uFF0C\u9ED8\u8BA4 3"),
28968
+ retry_backoff_ms: tool.schema.number().int().min(0).max(864e5).optional().describe("\u91CD\u8BD5\u9000\u907F\u57FA\u7840\u95F4\u9694 ms\uFF0C\u9ED8\u8BA4 30000"),
28969
+ timeout_ms: tool.schema.number().int().min(1e3).max(6048e5).optional().describe("\u4EFB\u52A1\u786C\u8D85\u65F6 ms\uFF1B\u672A\u4F20\u5219\u4F7F\u7528 Gateway \u9ED8\u8BA4\u503C"),
27910
28970
  cwd: tool.schema.string().optional().describe(
27911
28971
  "(\u5DF2\u5E9F\u5F03) \u5DE5\u4F5C\u76EE\u5F55\u3002\u7CFB\u7EDF\u4F1A\u81EA\u52A8\u8BB0\u5F55\u63D0\u4EA4\u4EFB\u52A1\u65F6\u7684 opencode run \u542F\u52A8\u76EE\u5F55\u3002"
27912
28972
  )
27913
28973
  },
27914
- async execute(args) {
28974
+ async execute(args, context) {
27915
28975
  try {
27916
- const submitCwd = process.cwd();
28976
+ assertRuntimeWritable();
27917
28977
  const task = await TaskService.add({
27918
28978
  name: args.name,
27919
28979
  agent: args.agent,
@@ -27924,7 +28984,7 @@ var SuperTaskPlugin = async () => {
27924
28984
  urgency: args.urgency ?? 3,
27925
28985
  batchId: args.batchId,
27926
28986
  dependsOn: args.dependsOn,
27927
- cwd: submitCwd,
28987
+ cwd: context.directory,
27928
28988
  maxRetries: args.max_retries,
27929
28989
  retryBackoffMs: args.retry_backoff_ms,
27930
28990
  timeoutMs: args.timeout_ms
@@ -27943,10 +29003,9 @@ var SuperTaskPlugin = async () => {
27943
29003
  args: {
27944
29004
  cwd: tool.schema.string().optional().describe("\u9879\u76EE\u9694\u79BB\uFF1A\u4F20\u5165\u5F53\u524D\u5DE5\u4F5C\u76EE\u5F55\uFF0C\u53EA\u8FD4\u56DE\u8BE5\u9879\u76EE\u7684\u4EFB\u52A1")
27945
29005
  },
27946
- async execute(args) {
29006
+ async execute(args, context) {
27947
29007
  try {
27948
- const scopeCwd = args.cwd ?? process.cwd();
27949
- const task = await TaskService.next({ cwd: scopeCwd });
29008
+ const task = await TaskService.next({ cwd: context.directory });
27950
29009
  if (task) {
27951
29010
  return JSON.stringify({
27952
29011
  id: task.id,
@@ -27969,85 +29028,6 @@ var SuperTaskPlugin = async () => {
27969
29028
  }
27970
29029
  }
27971
29030
  }),
27972
- // 开始执行任务
27973
- supertask_start: tool({
27974
- description: "\u6807\u8BB0\u4EFB\u52A1\u4E3A\u6B63\u5728\u6267\u884C\uFF08running\uFF09\u3002\u8BB0\u5F55\u5F00\u59CB\u65F6\u95F4\u3002",
27975
- args: {
27976
- id: tool.schema.number().describe("\u4EFB\u52A1 ID"),
27977
- cwd: tool.schema.string().optional().describe("\u9879\u76EE\u9694\u79BB\uFF1A\u4F20\u5165\u5F53\u524D\u5DE5\u4F5C\u76EE\u5F55\uFF0C\u53EA\u64CD\u4F5C\u8BE5\u9879\u76EE\u7684\u4EFB\u52A1")
27978
- },
27979
- async execute(args) {
27980
- try {
27981
- const scopeCwd = args.cwd ?? process.cwd();
27982
- const task = await TaskService.start(args.id, { cwd: scopeCwd });
27983
- if (task) return JSON.stringify({ id: task.id, status: task.status });
27984
- const existing = await TaskService.getById(args.id, { cwd: scopeCwd });
27985
- if (!existing) return JSON.stringify({ error: "Task not found" });
27986
- return JSON.stringify({
27987
- error: "Task status does not allow start",
27988
- id: existing.id,
27989
- status: existing.status,
27990
- message: `Only pending or retryable failed tasks can be started; current status is '${existing.status}'.`
27991
- });
27992
- } catch (error45) {
27993
- return JSON.stringify({
27994
- error: error45 instanceof Error ? error45.message : String(error45)
27995
- });
27996
- }
27997
- }
27998
- }),
27999
- // 完成任务
28000
- supertask_done: tool({
28001
- description: "\u6807\u8BB0\u4EFB\u52A1\u5B8C\u6210\uFF08done\uFF09\u3002\u8BB0\u5F55\u7ED3\u675F\u65F6\u95F4\u548C\u7ED3\u679C\u65E5\u5FD7\u3002",
28002
- args: {
28003
- id: tool.schema.number().describe("\u4EFB\u52A1 ID"),
28004
- log: tool.schema.string().optional().describe("\u6267\u884C\u7ED3\u679C\u65E5\u5FD7"),
28005
- cwd: tool.schema.string().optional().describe("\u9879\u76EE\u9694\u79BB\uFF1A\u4F20\u5165\u5F53\u524D\u5DE5\u4F5C\u76EE\u5F55\uFF0C\u53EA\u64CD\u4F5C\u8BE5\u9879\u76EE\u7684\u4EFB\u52A1")
28006
- },
28007
- async execute(args) {
28008
- try {
28009
- const scopeCwd = args.cwd ?? process.cwd();
28010
- const task = await TaskService.done(args.id, args.log, { cwd: scopeCwd });
28011
- if (task) {
28012
- return JSON.stringify({ id: task.id, status: task.status });
28013
- } else {
28014
- return JSON.stringify({ error: "Task not found" });
28015
- }
28016
- } catch (error45) {
28017
- return JSON.stringify({
28018
- error: error45 instanceof Error ? error45.message : String(error45)
28019
- });
28020
- }
28021
- }
28022
- }),
28023
- // 任务失败
28024
- supertask_fail: tool({
28025
- description: "\u6807\u8BB0\u4EFB\u52A1\u5931\u8D25\uFF08failed\uFF09\u3002\u8BB0\u5F55\u9519\u8BEF\u65E5\u5FD7\uFF0C\u81EA\u52A8\u589E\u52A0\u91CD\u8BD5\u8BA1\u6570\u3002",
28026
- args: {
28027
- id: tool.schema.number().describe("\u4EFB\u52A1 ID"),
28028
- log: tool.schema.string().optional().describe("\u9519\u8BEF\u65E5\u5FD7"),
28029
- cwd: tool.schema.string().optional().describe("\u9879\u76EE\u9694\u79BB\uFF1A\u4F20\u5165\u5F53\u524D\u5DE5\u4F5C\u76EE\u5F55\uFF0C\u53EA\u64CD\u4F5C\u8BE5\u9879\u76EE\u7684\u4EFB\u52A1")
28030
- },
28031
- async execute(args) {
28032
- try {
28033
- const scopeCwd = args.cwd ?? process.cwd();
28034
- const task = await TaskService.fail(args.id, args.log, { cwd: scopeCwd });
28035
- if (task) {
28036
- return JSON.stringify({
28037
- id: task.id,
28038
- status: task.status,
28039
- retryCount: task.retryCount
28040
- });
28041
- } else {
28042
- return JSON.stringify({ error: "Task not found" });
28043
- }
28044
- } catch (error45) {
28045
- return JSON.stringify({
28046
- error: error45 instanceof Error ? error45.message : String(error45)
28047
- });
28048
- }
28049
- }
28050
- }),
28051
29031
  // 查看统计
28052
29032
  supertask_status: tool({
28053
29033
  description: "\u67E5\u770B\u4EFB\u52A1\u961F\u5217\u7EDF\u8BA1\u3002\u53EF\u6309\u6279\u6B21\u7B5B\u9009\u3002",
@@ -28055,10 +29035,9 @@ var SuperTaskPlugin = async () => {
28055
29035
  batchId: tool.schema.string().optional().describe("\u6309\u6279\u6B21\u7B5B\u9009"),
28056
29036
  cwd: tool.schema.string().optional().describe("\u9879\u76EE\u9694\u79BB\uFF1A\u4F20\u5165\u5F53\u524D\u5DE5\u4F5C\u76EE\u5F55\uFF0C\u53EA\u7EDF\u8BA1\u8BE5\u9879\u76EE\u7684\u4EFB\u52A1")
28057
29037
  },
28058
- async execute(args) {
29038
+ async execute(args, context) {
28059
29039
  try {
28060
- const scopeCwd = args.cwd ?? process.cwd();
28061
- const stats = await TaskService.stats({ batchId: args.batchId, cwd: scopeCwd });
29040
+ const stats = await TaskService.stats({ batchId: args.batchId, cwd: context.directory });
28062
29041
  return JSON.stringify(stats);
28063
29042
  } catch (error45) {
28064
29043
  return JSON.stringify({
@@ -28071,22 +29050,22 @@ var SuperTaskPlugin = async () => {
28071
29050
  supertask_retry: tool({
28072
29051
  description: "\u91CD\u8BD5\u5931\u8D25\u7684\u4EFB\u52A1\u3002\u5C06 failed \u72B6\u6001\u91CD\u7F6E\u4E3A pending\uFF0C\u81EA\u52A8\u6E05\u9664\u5F00\u59CB/\u7ED3\u675F\u65F6\u95F4\u3002",
28073
29052
  args: {
28074
- id: tool.schema.number().optional().describe("\u4EFB\u52A1 ID"),
29053
+ id: tool.schema.number().int().positive().optional().describe("\u4EFB\u52A1 ID"),
28075
29054
  batchId: tool.schema.string().optional().describe("\u6279\u6B21 ID\uFF08\u6279\u91CF\u91CD\u8BD5\uFF09"),
28076
29055
  cwd: tool.schema.string().optional().describe("\u9879\u76EE\u9694\u79BB\uFF1A\u4F20\u5165\u5F53\u524D\u5DE5\u4F5C\u76EE\u5F55\uFF0C\u53EA\u64CD\u4F5C\u8BE5\u9879\u76EE\u7684\u4EFB\u52A1")
28077
29056
  },
28078
- async execute(args) {
29057
+ async execute(args, context) {
28079
29058
  try {
28080
- const scopeCwd = args.cwd ?? process.cwd();
29059
+ assertRuntimeWritable();
28081
29060
  if (args.id !== void 0) {
28082
- const task = await TaskService.retry(args.id, { cwd: scopeCwd });
29061
+ const task = await TaskService.retry(args.id, { cwd: context.directory });
28083
29062
  if (task) {
28084
29063
  return JSON.stringify({ id: task.id, status: task.status });
28085
29064
  } else {
28086
29065
  return JSON.stringify({ error: "Task not found or not failed" });
28087
29066
  }
28088
29067
  } else if (args.batchId !== void 0) {
28089
- const count = await TaskService.retryBatch(args.batchId, { cwd: scopeCwd });
29068
+ const count = await TaskService.retryBatch(args.batchId, { cwd: context.directory });
28090
29069
  return JSON.stringify({ retried: count, batchId: args.batchId });
28091
29070
  } else {
28092
29071
  return JSON.stringify({ error: "Please specify id or batchId" });
@@ -28102,16 +29081,15 @@ var SuperTaskPlugin = async () => {
28102
29081
  supertask_list: tool({
28103
29082
  description: "\u5217\u51FA\u6700\u8FD1\u7684\u4EFB\u52A1\u3002\u652F\u6301\u6309\u72B6\u6001\u7B5B\u9009\uFF0C\u6309\u521B\u5EFA\u65F6\u95F4\u5012\u5E8F\u3002",
28104
29083
  args: {
28105
- status: tool.schema.string().optional().describe("\u6309\u72B6\u6001\u7B5B\u9009\uFF1Apending/running/done/failed/cancelled"),
28106
- limit: tool.schema.number().optional().describe("\u8FD4\u56DE\u6570\u91CF\uFF0C\u9ED8\u8BA4 20"),
29084
+ status: tool.schema.enum(["pending", "running", "done", "failed", "dead_letter", "cancelled"]).optional().describe("\u6309\u72B6\u6001\u7B5B\u9009"),
29085
+ limit: tool.schema.number().int().min(1).max(1e3).optional().describe("\u8FD4\u56DE\u6570\u91CF\uFF0C\u9ED8\u8BA4 20"),
28107
29086
  cwd: tool.schema.string().optional().describe("\u9879\u76EE\u9694\u79BB\uFF1A\u4F20\u5165\u5F53\u524D\u5DE5\u4F5C\u76EE\u5F55\uFF0C\u53EA\u8FD4\u56DE\u8BE5\u9879\u76EE\u7684\u4EFB\u52A1")
28108
29087
  },
28109
- async execute(args) {
29088
+ async execute(args, context) {
28110
29089
  try {
28111
- const scopeCwd = args.cwd ?? process.cwd();
28112
29090
  const tasks3 = await TaskService.list({
28113
29091
  status: args.status,
28114
- cwd: scopeCwd,
29092
+ cwd: context.directory,
28115
29093
  limit: args.limit ?? 20
28116
29094
  });
28117
29095
  return JSON.stringify(tasks3);
@@ -28126,13 +29104,12 @@ var SuperTaskPlugin = async () => {
28126
29104
  supertask_get: tool({
28127
29105
  description: "\u83B7\u53D6\u6307\u5B9A ID \u7684\u4EFB\u52A1\u8BE6\u60C5\u3002",
28128
29106
  args: {
28129
- id: tool.schema.number().describe("\u4EFB\u52A1 ID"),
29107
+ id: tool.schema.number().int().positive().describe("\u4EFB\u52A1 ID"),
28130
29108
  cwd: tool.schema.string().optional().describe("\u9879\u76EE\u9694\u79BB\uFF1A\u4F20\u5165\u5F53\u524D\u5DE5\u4F5C\u76EE\u5F55\uFF0C\u53EA\u8FD4\u56DE\u8BE5\u9879\u76EE\u7684\u4EFB\u52A1")
28131
29109
  },
28132
- async execute(args) {
29110
+ async execute(args, context) {
28133
29111
  try {
28134
- const scopeCwd = args.cwd ?? process.cwd();
28135
- const task = await TaskService.getById(args.id, { cwd: scopeCwd });
29112
+ const task = await TaskService.getById(args.id, { cwd: context.directory });
28136
29113
  if (task) {
28137
29114
  return JSON.stringify({
28138
29115
  id: task.id,
@@ -28158,13 +29135,13 @@ var SuperTaskPlugin = async () => {
28158
29135
  supertask_schedule: tool({
28159
29136
  description: "\u521B\u5EFA\u8C03\u5EA6\u6A21\u677F\uFF0C\u7528\u4E8E\u5B9A\u65F6/\u5EF6\u8FDF/\u5FAA\u73AF\u6267\u884C\u4EFB\u52A1\u3002\u652F\u6301 cron \u8868\u8FBE\u5F0F\u3001\u4E00\u6B21\u6027\u5EF6\u8FDF\u548C\u56FA\u5B9A\u95F4\u9694\u5FAA\u73AF\u3002Gateway \u4F1A\u6309\u6A21\u677F\u81EA\u52A8\u751F\u6210\u4EFB\u52A1\u5230\u961F\u5217\u3002",
28160
29137
  args: {
28161
- name: tool.schema.string().describe("\u6A21\u677F\u540D\u79F0"),
28162
- agent: tool.schema.string().describe("\u6267\u884C\u7684 Agent \u540D\u79F0"),
28163
- prompt: tool.schema.string().describe("\u53D1\u9001\u7ED9 Agent \u7684\u5B8C\u6574\u63D0\u793A\u8BCD"),
29138
+ name: tool.schema.string().trim().min(1).describe("\u6A21\u677F\u540D\u79F0"),
29139
+ agent: tool.schema.string().trim().min(1).describe("\u6267\u884C\u7684 Agent \u540D\u79F0"),
29140
+ prompt: tool.schema.string().trim().min(1).describe("\u53D1\u9001\u7ED9 Agent \u7684\u5B8C\u6574\u63D0\u793A\u8BCD"),
28164
29141
  model: tool.schema.string().optional().describe("\u4F7F\u7528\u7684\u6A21\u578B"),
28165
- category: tool.schema.string().optional().describe("\u4EFB\u52A1\u5206\u7C7B\uFF1Atranslate/generate/review/test/general"),
28166
- importance: tool.schema.number().optional().describe("\u91CD\u8981\u7A0B\u5EA6 1-5"),
28167
- urgency: tool.schema.number().optional().describe("\u7D27\u6025\u7A0B\u5EA6 1-5"),
29142
+ category: tool.schema.enum(["translate", "generate", "review", "test", "general"]).optional().describe("\u4EFB\u52A1\u5206\u7C7B"),
29143
+ importance: tool.schema.number().int().min(1).max(5).optional().describe("\u91CD\u8981\u7A0B\u5EA6 1-5"),
29144
+ urgency: tool.schema.number().int().min(1).max(5).optional().describe("\u7D27\u6025\u7A0B\u5EA6 1-5"),
28168
29145
  batchId: tool.schema.string().optional().describe("\u6A21\u677F\u751F\u6210\u7684\u4EFB\u52A1\u5F52\u5C5E\u7684\u6279\u6B21 ID"),
28169
29146
  schedule: tool.schema.object({
28170
29147
  type: tool.schema.enum(["cron", "delayed", "recurring"]).describe("\u8C03\u5EA6\u7C7B\u578B"),
@@ -28172,13 +29149,14 @@ var SuperTaskPlugin = async () => {
28172
29149
  delay: tool.schema.string().optional().describe("\u5EF6\u8FDF\u65F6\u95F4\uFF08delayed \u7C7B\u578B\u5FC5\u586B\uFF09\uFF0C\u53CB\u597D\u683C\u5F0F\u5982 '30s' '5min' '1h' '2d'\uFF0C\u4E5F\u652F\u6301 ISO 8601 duration \u5982 'PT30M'"),
28173
29150
  interval: tool.schema.string().optional().describe("\u5FAA\u73AF\u95F4\u9694\uFF08recurring \u7C7B\u578B\u5FC5\u586B\uFF09\uFF0C\u53CB\u597D\u683C\u5F0F\u5982 '1h' '30min' '5s'\uFF0C\u4E5F\u652F\u6301 ISO 8601 duration \u5982 'PT1H'")
28174
29151
  }).describe("\u8C03\u5EA6\u914D\u7F6E"),
28175
- max_instances: tool.schema.number().optional().describe("\u6700\u5927\u5E76\u53D1\u5B9E\u4F8B\u6570\uFF0C\u9ED8\u8BA4 1"),
28176
- max_retries: tool.schema.number().optional().describe("\u514B\u9686\u7ED9 task \u7684\u6700\u5927\u91CD\u8BD5\u6B21\u6570\uFF0C\u9ED8\u8BA4 3"),
28177
- retry_backoff_ms: tool.schema.number().optional().describe("\u514B\u9686\u7ED9 task \u7684\u9000\u907F\u57FA\u7840\u95F4\u9694 ms\uFF0C\u9ED8\u8BA4 30000"),
28178
- timeout_ms: tool.schema.number().optional().describe("\u514B\u9686\u7ED9 task \u7684\u786C\u8D85\u65F6 ms\uFF1B\u672A\u4F20\u5219\u4F7F\u7528 Gateway \u9ED8\u8BA4\u503C")
29152
+ max_instances: tool.schema.number().int().min(1).max(1e3).optional().describe("\u6700\u5927\u5E76\u53D1\u5B9E\u4F8B\u6570\uFF0C\u9ED8\u8BA4 1"),
29153
+ max_retries: tool.schema.number().int().min(0).max(1e3).optional().describe("\u514B\u9686\u7ED9 task \u7684\u6700\u5927\u91CD\u8BD5\u6B21\u6570\uFF0C\u9ED8\u8BA4 3"),
29154
+ retry_backoff_ms: tool.schema.number().int().min(0).max(864e5).optional().describe("\u514B\u9686\u7ED9 task \u7684\u9000\u907F\u57FA\u7840\u95F4\u9694 ms\uFF0C\u9ED8\u8BA4 30000"),
29155
+ timeout_ms: tool.schema.number().int().min(1e3).max(6048e5).optional().describe("\u514B\u9686\u7ED9 task \u7684\u786C\u8D85\u65F6 ms\uFF1B\u672A\u4F20\u5219\u4F7F\u7528 Gateway \u9ED8\u8BA4\u503C")
28179
29156
  },
28180
- async execute(args) {
29157
+ async execute(args, context) {
28181
29158
  try {
29159
+ assertRuntimeWritable();
28182
29160
  if (!args.schedule) {
28183
29161
  return JSON.stringify({ error: "schedule is required" });
28184
29162
  }
@@ -28207,7 +29185,7 @@ var SuperTaskPlugin = async () => {
28207
29185
  category: args.category ?? "general",
28208
29186
  importance: args.importance ?? 3,
28209
29187
  urgency: args.urgency ?? 3,
28210
- cwd: process.cwd(),
29188
+ cwd: context.directory,
28211
29189
  batchId: args.batchId,
28212
29190
  scheduleType,
28213
29191
  cronExpr,
@@ -28236,19 +29214,47 @@ var SuperTaskPlugin = async () => {
28236
29214
  description: "\u5347\u7EA7 SuperTask \u63D2\u4EF6\u3002\u901A\u8FC7 OpenCode \u5237\u65B0\u63D2\u4EF6\u7F13\u5B58\uFF0C\u6821\u9A8C\u65B0\u7248\u6784\u5EFA\u4EA7\u7269\u540E\u91CD\u542F Gateway\u3002\u5F53\u7528\u6237\u8BF4'\u5347\u7EA7\u63D2\u4EF6'\u3001'\u66F4\u65B0 supertask'\u3001'upgrade'\u65F6\u4F7F\u7528\u3002",
28237
29215
  args: {},
28238
29216
  async execute() {
29217
+ if (process.env[MANAGED_RUN_ENV] === MANAGED_RUN_ENV_VALUE) {
29218
+ return JSON.stringify({
29219
+ success: false,
29220
+ error: "Gateway \u7BA1\u7406\u7684\u961F\u5217\u4EFB\u52A1\u4E0D\u80FD\u5347\u7EA7 SuperTask\uFF0C\u5426\u5219\u4F1A\u7EC8\u6B62\u627F\u8F7D\u5F53\u524D\u4EFB\u52A1\u7684 Gateway\u3002",
29221
+ hint: "\u8BF7\u4ECE\u5916\u90E8\u7EC8\u7AEF\u6267\u884C `supertask upgrade`\uFF0C\u6216\u5728\u975E\u961F\u5217 OpenCode \u4F1A\u8BDD\u4E2D\u8C03\u7528\u5347\u7EA7\u5DE5\u5177\u3002"
29222
+ });
29223
+ }
28239
29224
  try {
28240
29225
  console.log("[supertask] Updating OpenCode plugin cache...");
29226
+ const previousVersion = getPackageVersion();
28241
29227
  let installed;
28242
29228
  try {
28243
29229
  installed = installLatestPlugin();
28244
29230
  } catch (updateError) {
29231
+ let detail = updateError instanceof Error ? updateError.message : String(updateError);
29232
+ try {
29233
+ installPluginVersion(previousVersion);
29234
+ } catch (rollbackError) {
29235
+ detail += `; OpenCode \u63D2\u4EF6\u56DE\u6EDA\u5931\u8D25: ${rollbackError instanceof Error ? rollbackError.message : String(rollbackError)}`;
29236
+ }
28245
29237
  return JSON.stringify({
28246
29238
  success: false,
28247
- error: updateError instanceof Error ? updateError.message : String(updateError),
29239
+ error: detail,
28248
29240
  hint: "Query npm dist-tags.latest, then install that exact version with opencode plugin."
28249
29241
  });
28250
29242
  }
28251
- const result = upgrade(installed);
29243
+ let result;
29244
+ try {
29245
+ result = upgrade(installed);
29246
+ } catch (upgradeError) {
29247
+ try {
29248
+ if (previousVersion !== installed.version) {
29249
+ installPluginVersion(previousVersion);
29250
+ }
29251
+ } catch (rollbackError) {
29252
+ const original = upgradeError instanceof Error ? upgradeError.message : String(upgradeError);
29253
+ const rollback = rollbackError instanceof Error ? rollbackError.message : String(rollbackError);
29254
+ throw new Error(`${original}; Gateway \u5DF2\u56DE\u6EDA\uFF0C\u4F46 OpenCode \u63D2\u4EF6\u56DE\u6EDA\u5931\u8D25: ${rollback}`);
29255
+ }
29256
+ throw upgradeError;
29257
+ }
28252
29258
  return JSON.stringify({
28253
29259
  success: true,
28254
29260
  before: result.before,
@@ -28268,6 +29274,7 @@ var SuperTaskPlugin = async () => {
28268
29274
  };
28269
29275
  };
28270
29276
  export {
28271
- SuperTaskPlugin
29277
+ SuperTaskPlugin,
29278
+ shouldAttemptGatewayReplacement
28272
29279
  };
28273
29280
  //# sourceMappingURL=supertask.js.map