oh-my-opencode 3.0.0-beta.10 → 3.0.0-beta.11

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -19111,6 +19111,80 @@ ${ULTRAWORK_PLANNER_SECTION}
19111
19111
 
19112
19112
  [CODE RED] Maximum precision required. Ultrathink before acting.
19113
19113
 
19114
+ ## **ABSOLUTE CERTAINTY REQUIRED - DO NOT SKIP THIS**
19115
+
19116
+ **YOU MUST NOT START ANY IMPLEMENTATION UNTIL YOU ARE 100% CERTAIN.**
19117
+
19118
+ | **BEFORE YOU WRITE A SINGLE LINE OF CODE, YOU MUST:** |
19119
+ |-------------------------------------------------------|
19120
+ | **FULLY UNDERSTAND** what the user ACTUALLY wants (not what you ASSUME they want) |
19121
+ | **EXPLORE** the codebase to understand existing patterns, architecture, and context |
19122
+ | **HAVE A CRYSTAL CLEAR WORK PLAN** - if your plan is vague, YOUR WORK WILL FAIL |
19123
+ | **RESOLVE ALL AMBIGUITY** - if ANYTHING is unclear, ASK or INVESTIGATE |
19124
+
19125
+ ### **MANDATORY CERTAINTY PROTOCOL**
19126
+
19127
+ **IF YOU ARE NOT 100% CERTAIN:**
19128
+
19129
+ 1. **THINK DEEPLY** - What is the user's TRUE intent? What problem are they REALLY trying to solve?
19130
+ 2. **EXPLORE THOROUGHLY** - Fire explore/librarian agents to gather ALL relevant context
19131
+ 3. **CONSULT ORACLE** - For architecture decisions, complex logic, or when you're stuck
19132
+ 4. **ASK THE USER** - If ambiguity remains after exploration, ASK. Don't guess.
19133
+
19134
+ **SIGNS YOU ARE NOT READY TO IMPLEMENT:**
19135
+ - You're making assumptions about requirements
19136
+ - You're unsure which files to modify
19137
+ - You don't understand how existing code works
19138
+ - Your plan has "probably" or "maybe" in it
19139
+ - You can't explain the exact steps you'll take
19140
+
19141
+ **WHEN IN DOUBT:**
19142
+ \`\`\`
19143
+ delegate_task(agent="explore", prompt="Find [X] patterns in codebase", background=true)
19144
+ delegate_task(agent="librarian", prompt="Find docs/examples for [Y]", background=true)
19145
+ delegate_task(agent="oracle", prompt="Review my approach: [describe plan]")
19146
+ \`\`\`
19147
+
19148
+ **ONLY AFTER YOU HAVE:**
19149
+ - Gathered sufficient context via agents
19150
+ - Resolved all ambiguities
19151
+ - Created a precise, step-by-step work plan
19152
+ - Achieved 100% confidence in your understanding
19153
+
19154
+ **...THEN AND ONLY THEN MAY YOU BEGIN IMPLEMENTATION.**
19155
+
19156
+ ---
19157
+
19158
+ ## **NO EXCUSES. NO COMPROMISES. DELIVER WHAT WAS ASKED.**
19159
+
19160
+ **THE USER'S ORIGINAL REQUEST IS SACRED. YOU MUST FULFILL IT EXACTLY.**
19161
+
19162
+ | VIOLATION | CONSEQUENCE |
19163
+ |-----------|-------------|
19164
+ | "I couldn't because..." | **UNACCEPTABLE.** Find a way or ask for help. |
19165
+ | "This is a simplified version..." | **UNACCEPTABLE.** Deliver the FULL implementation. |
19166
+ | "You can extend this later..." | **UNACCEPTABLE.** Finish it NOW. |
19167
+ | "Due to limitations..." | **UNACCEPTABLE.** Use agents, tools, whatever it takes. |
19168
+ | "I made some assumptions..." | **UNACCEPTABLE.** You should have asked FIRST. |
19169
+
19170
+ **THERE ARE NO VALID EXCUSES FOR:**
19171
+ - Delivering partial work
19172
+ - Changing scope without explicit user approval
19173
+ - Making unauthorized simplifications
19174
+ - Stopping before the task is 100% complete
19175
+ - Compromising on any stated requirement
19176
+
19177
+ **IF YOU ENCOUNTER A BLOCKER:**
19178
+ 1. **DO NOT** give up
19179
+ 2. **DO NOT** deliver a compromised version
19180
+ 3. **DO** consult oracle for solutions
19181
+ 4. **DO** ask the user for guidance
19182
+ 5. **DO** explore alternative approaches
19183
+
19184
+ **THE USER ASKED FOR X. DELIVER EXACTLY X. PERIOD.**
19185
+
19186
+ ---
19187
+
19114
19188
  YOU MUST LEVERAGE ALL AVAILABLE AGENTS TO THEIR FULLEST POTENTIAL.
19115
19189
  TELL THE USER WHAT AGENTS YOU WILL LEVERAGE NOW TO SATISFY USER'S REQUEST.
19116
19190
 
@@ -22298,7 +22372,7 @@ function createPrometheusMdOnlyHook(ctx) {
22298
22372
  filePath,
22299
22373
  agent: agentName
22300
22374
  });
22301
- throw new Error(`[${HOOK_NAME4}] Prometheus (Planner) can only write/edit .md files inside .sisyphus/ directory. ` + `Attempted to modify: ${filePath}. ` + `Prometheus is a READ-ONLY planner. Use /start-work to execute the plan.`);
22375
+ throw new Error(`[${HOOK_NAME4}] Prometheus (Planner) can only write/edit .md files inside .sisyphus/ directory. ` + `Attempted to modify: ${filePath}. ` + `Prometheus is a READ-ONLY planner. Use /start-work to execute the plan. ` + `APOLOGIZE TO THE USER, REMIND OF YOUR PLAN WRITING PROCESSES, TELL USER WHAT YOU WILL GOING TO DO AS THE PROCESS, WRITE THE PLAN`);
22302
22376
  }
22303
22377
  log(`[${HOOK_NAME4}] Allowed: .sisyphus/*.md write permitted`, {
22304
22378
  sessionID: input.sessionID,
@@ -40050,7 +40124,14 @@ function truncateText(text, maxLength) {
40050
40124
  return text.slice(0, maxLength) + "...";
40051
40125
  }
40052
40126
  function formatTaskStatus(task) {
40053
- const duration3 = formatDuration(task.startedAt, task.completedAt);
40127
+ let duration3;
40128
+ if (task.status === "pending" && task.queuedAt) {
40129
+ duration3 = formatDuration(task.queuedAt, undefined);
40130
+ } else if (task.startedAt) {
40131
+ duration3 = formatDuration(task.startedAt, task.completedAt);
40132
+ } else {
40133
+ duration3 = "N/A";
40134
+ }
40054
40135
  const promptPreview = truncateText(task.prompt, 500);
40055
40136
  let progressSection = "";
40056
40137
  if (task.progress?.lastTool) {
@@ -40070,7 +40151,11 @@ ${truncated}
40070
40151
  \`\`\``;
40071
40152
  }
40072
40153
  let statusNote = "";
40073
- if (task.status === "running") {
40154
+ if (task.status === "pending") {
40155
+ statusNote = `
40156
+
40157
+ > **Queued**: Task is waiting for a concurrency slot to become available.`;
40158
+ } else if (task.status === "running") {
40074
40159
  statusNote = `
40075
40160
 
40076
40161
  > **Note**: No need to wait explicitly - the system will notify you when this task completes.`;
@@ -40079,6 +40164,7 @@ ${truncated}
40079
40164
 
40080
40165
  > **Failed**: The task encountered an error. Check the last message for details.`;
40081
40166
  }
40167
+ const durationLabel = task.status === "pending" ? "Queued for" : "Duration";
40082
40168
  return `# Task Status
40083
40169
 
40084
40170
  | Field | Value |
@@ -40087,7 +40173,7 @@ ${truncated}
40087
40173
  | Description | ${task.description} |
40088
40174
  | Agent | ${task.agent} |
40089
40175
  | Status | **${task.status}** |
40090
- | Duration | ${duration3} |
40176
+ | ${durationLabel} | ${duration3} |
40091
40177
  | Session ID | \`${task.sessionID}\` |${progressSection}
40092
40178
  ${statusNote}
40093
40179
  ## Original Prompt
@@ -40097,6 +40183,9 @@ ${promptPreview}
40097
40183
  \`\`\`${lastMessageSection}`;
40098
40184
  }
40099
40185
  async function formatTaskResult(task, client2) {
40186
+ if (!task.sessionID) {
40187
+ return `Error: Task has no sessionID`;
40188
+ }
40100
40189
  const messagesResult = await client2.session.messages({
40101
40190
  path: { id: task.sessionID }
40102
40191
  });
@@ -40109,7 +40198,7 @@ async function formatTaskResult(task, client2) {
40109
40198
 
40110
40199
  Task ID: ${task.id}
40111
40200
  Description: ${task.description}
40112
- Duration: ${formatDuration(task.startedAt, task.completedAt)}
40201
+ Duration: ${formatDuration(task.startedAt ?? new Date, task.completedAt)}
40113
40202
  Session ID: ${task.sessionID}
40114
40203
 
40115
40204
  ---
@@ -40122,7 +40211,7 @@ Session ID: ${task.sessionID}
40122
40211
 
40123
40212
  Task ID: ${task.id}
40124
40213
  Description: ${task.description}
40125
- Duration: ${formatDuration(task.startedAt, task.completedAt)}
40214
+ Duration: ${formatDuration(task.startedAt ?? new Date, task.completedAt)}
40126
40215
  Session ID: ${task.sessionID}
40127
40216
 
40128
40217
  ---
@@ -40136,7 +40225,7 @@ Session ID: ${task.sessionID}
40136
40225
  });
40137
40226
  const newMessages = consumeNewMessages(task.sessionID, sortedMessages);
40138
40227
  if (newMessages.length === 0) {
40139
- const duration4 = formatDuration(task.startedAt, task.completedAt);
40228
+ const duration4 = formatDuration(task.startedAt ?? new Date, task.completedAt);
40140
40229
  return `Task Result
40141
40230
 
40142
40231
  Task ID: ${task.id}
@@ -40170,7 +40259,7 @@ Session ID: ${task.sessionID}
40170
40259
  const textContent = extractedContent.filter((text) => text.length > 0).join(`
40171
40260
 
40172
40261
  `);
40173
- const duration3 = formatDuration(task.startedAt, task.completedAt);
40262
+ const duration3 = formatDuration(task.startedAt ?? new Date, task.completedAt);
40174
40263
  return `Task Result
40175
40264
 
40176
40265
  Task ID: ${task.id}
@@ -40249,20 +40338,25 @@ function createBackgroundCancel(manager, client2) {
40249
40338
  }
40250
40339
  if (cancelAll) {
40251
40340
  const tasks = manager.getAllDescendantTasks(toolContext.sessionID);
40252
- const runningTasks = tasks.filter((t) => t.status === "running");
40253
- if (runningTasks.length === 0) {
40254
- return `\u2705 No running background tasks to cancel.`;
40341
+ const cancellableTasks = tasks.filter((t) => t.status === "running" || t.status === "pending");
40342
+ if (cancellableTasks.length === 0) {
40343
+ return `\u2705 No running or pending background tasks to cancel.`;
40255
40344
  }
40256
40345
  const results = [];
40257
- for (const task2 of runningTasks) {
40258
- client2.session.abort({
40259
- path: { id: task2.sessionID }
40260
- }).catch(() => {});
40261
- task2.status = "cancelled";
40262
- task2.completedAt = new Date;
40263
- results.push(`- ${task2.id}: ${task2.description}`);
40346
+ for (const task2 of cancellableTasks) {
40347
+ if (task2.status === "pending") {
40348
+ manager.cancelPendingTask(task2.id);
40349
+ results.push(`- ${task2.id}: ${task2.description} (pending)`);
40350
+ } else if (task2.sessionID) {
40351
+ client2.session.abort({
40352
+ path: { id: task2.sessionID }
40353
+ }).catch(() => {});
40354
+ task2.status = "cancelled";
40355
+ task2.completedAt = new Date;
40356
+ results.push(`- ${task2.id}: ${task2.description} (running)`);
40357
+ }
40264
40358
  }
40265
- return `\u2705 Cancelled ${runningTasks.length} background task(s):
40359
+ return `\u2705 Cancelled ${cancellableTasks.length} background task(s):
40266
40360
 
40267
40361
  ${results.join(`
40268
40362
  `)}`;
@@ -40271,13 +40365,26 @@ ${results.join(`
40271
40365
  if (!task) {
40272
40366
  return `\u274C Task not found: ${args.taskId}`;
40273
40367
  }
40274
- if (task.status !== "running") {
40368
+ if (task.status !== "running" && task.status !== "pending") {
40275
40369
  return `\u274C Cannot cancel task: current status is "${task.status}".
40276
- Only running tasks can be cancelled.`;
40370
+ Only running or pending tasks can be cancelled.`;
40371
+ }
40372
+ if (task.status === "pending") {
40373
+ const cancelled = manager.cancelPendingTask(task.id);
40374
+ if (!cancelled) {
40375
+ return `\u274C Failed to cancel pending task: ${task.id}`;
40376
+ }
40377
+ return `\u2705 Pending task cancelled successfully
40378
+
40379
+ Task ID: ${task.id}
40380
+ Description: ${task.description}
40381
+ Status: ${task.status}`;
40382
+ }
40383
+ if (task.sessionID) {
40384
+ client2.session.abort({
40385
+ path: { id: task.sessionID }
40386
+ }).catch(() => {});
40277
40387
  }
40278
- client2.session.abort({
40279
- path: { id: task.sessionID }
40280
- }).catch(() => {});
40281
40388
  task.status = "cancelled";
40282
40389
  task.completedAt = new Date;
40283
40390
  return `\u2705 Task cancelled successfully
@@ -40817,7 +40924,8 @@ class TaskToastManager {
40817
40924
  for (const task of queued) {
40818
40925
  const bgIcon = task.isBackground ? "\u23F3" : "\u23F8\uFE0F";
40819
40926
  const skillsInfo = task.skills?.length ? ` [${task.skills.join(", ")}]` : "";
40820
- lines.push(`${bgIcon} ${task.description} (${task.agent})${skillsInfo}`);
40927
+ const isNew = task.id === newTask.id ? " \u2190 NEW" : "";
40928
+ lines.push(`${bgIcon} ${task.description} (${task.agent})${skillsInfo} - Queued${isNew}`);
40821
40929
  }
40822
40930
  }
40823
40931
  return lines.join(`
@@ -41616,6 +41724,8 @@ class BackgroundManager {
41616
41724
  concurrencyManager;
41617
41725
  shutdownTriggered = false;
41618
41726
  config;
41727
+ queuesByKey = new Map;
41728
+ processingKeys = new Set;
41619
41729
  constructor(ctx, config3) {
41620
41730
  this.tasks = new Map;
41621
41731
  this.notifications = new Map;
@@ -41636,8 +41746,78 @@ class BackgroundManager {
41636
41746
  if (!input.agent || input.agent.trim() === "") {
41637
41747
  throw new Error("Agent parameter is required");
41638
41748
  }
41639
- const concurrencyKey = input.agent;
41640
- await this.concurrencyManager.acquire(concurrencyKey);
41749
+ const task = {
41750
+ id: `bg_${crypto.randomUUID().slice(0, 8)}`,
41751
+ status: "pending",
41752
+ queuedAt: new Date,
41753
+ description: input.description,
41754
+ prompt: input.prompt,
41755
+ agent: input.agent,
41756
+ parentSessionID: input.parentSessionID,
41757
+ parentMessageID: input.parentMessageID,
41758
+ parentModel: input.parentModel,
41759
+ parentAgent: input.parentAgent,
41760
+ model: input.model
41761
+ };
41762
+ this.tasks.set(task.id, task);
41763
+ if (input.parentSessionID) {
41764
+ const pending = this.pendingByParent.get(input.parentSessionID) ?? new Set;
41765
+ pending.add(task.id);
41766
+ this.pendingByParent.set(input.parentSessionID, pending);
41767
+ }
41768
+ const key = this.getConcurrencyKeyFromInput(input);
41769
+ const queue = this.queuesByKey.get(key) ?? [];
41770
+ queue.push({ task, input });
41771
+ this.queuesByKey.set(key, queue);
41772
+ log("[background-agent] Task queued:", { taskId: task.id, key, queueLength: queue.length });
41773
+ const toastManager = getTaskToastManager();
41774
+ if (toastManager) {
41775
+ toastManager.addTask({
41776
+ id: task.id,
41777
+ description: input.description,
41778
+ agent: input.agent,
41779
+ isBackground: true,
41780
+ status: "queued",
41781
+ skills: input.skills
41782
+ });
41783
+ }
41784
+ this.processKey(key);
41785
+ return task;
41786
+ }
41787
+ async processKey(key) {
41788
+ if (this.processingKeys.has(key)) {
41789
+ return;
41790
+ }
41791
+ this.processingKeys.add(key);
41792
+ try {
41793
+ const queue = this.queuesByKey.get(key);
41794
+ while (queue && queue.length > 0) {
41795
+ const item = queue[0];
41796
+ await this.concurrencyManager.acquire(key);
41797
+ if (item.task.status === "cancelled") {
41798
+ this.concurrencyManager.release(key);
41799
+ queue.shift();
41800
+ continue;
41801
+ }
41802
+ try {
41803
+ await this.startTask(item);
41804
+ } catch (error45) {
41805
+ log("[background-agent] Error starting task:", error45);
41806
+ }
41807
+ queue.shift();
41808
+ }
41809
+ } finally {
41810
+ this.processingKeys.delete(key);
41811
+ }
41812
+ }
41813
+ async startTask(item) {
41814
+ const { task, input } = item;
41815
+ log("[background-agent] Starting task:", {
41816
+ taskId: task.id,
41817
+ agent: input.agent,
41818
+ model: input.model
41819
+ });
41820
+ const concurrencyKey = this.getConcurrencyKeyFromInput(input);
41641
41821
  const parentSession = await this.client.session.get({
41642
41822
  path: { id: input.parentSessionID }
41643
41823
  }).catch((err) => {
@@ -41664,41 +41844,20 @@ class BackgroundManager {
41664
41844
  }
41665
41845
  const sessionID = createResult.data.id;
41666
41846
  subagentSessions.add(sessionID);
41667
- const task = {
41668
- id: `bg_${crypto.randomUUID().slice(0, 8)}`,
41669
- sessionID,
41670
- parentSessionID: input.parentSessionID,
41671
- parentMessageID: input.parentMessageID,
41672
- description: input.description,
41673
- prompt: input.prompt,
41674
- agent: input.agent,
41675
- status: "running",
41676
- startedAt: new Date,
41677
- progress: {
41678
- toolCalls: 0,
41679
- lastUpdate: new Date
41680
- },
41681
- parentModel: input.parentModel,
41682
- parentAgent: input.parentAgent,
41683
- model: input.model,
41684
- concurrencyKey,
41685
- concurrencyGroup: concurrencyKey
41847
+ task.status = "running";
41848
+ task.startedAt = new Date;
41849
+ task.sessionID = sessionID;
41850
+ task.progress = {
41851
+ toolCalls: 0,
41852
+ lastUpdate: new Date
41686
41853
  };
41687
- this.tasks.set(task.id, task);
41854
+ task.concurrencyKey = concurrencyKey;
41855
+ task.concurrencyGroup = concurrencyKey;
41688
41856
  this.startPolling();
41689
- const pending = this.pendingByParent.get(input.parentSessionID) ?? new Set;
41690
- pending.add(task.id);
41691
- this.pendingByParent.set(input.parentSessionID, pending);
41692
41857
  log("[background-agent] Launching task:", { taskId: task.id, sessionID, agent: input.agent });
41693
41858
  const toastManager = getTaskToastManager();
41694
41859
  if (toastManager) {
41695
- toastManager.addTask({
41696
- id: task.id,
41697
- description: input.description,
41698
- agent: input.agent,
41699
- isBackground: true,
41700
- skills: input.skills
41701
- });
41860
+ toastManager.updateTask(task.id, "running");
41702
41861
  }
41703
41862
  log("[background-agent] Calling prompt (fire-and-forget) for launch with:", {
41704
41863
  sessionID,
@@ -41743,7 +41902,6 @@ class BackgroundManager {
41743
41902
  });
41744
41903
  }
41745
41904
  });
41746
- return task;
41747
41905
  }
41748
41906
  getTask(id) {
41749
41907
  return this.tasks.get(id);
@@ -41762,8 +41920,10 @@ class BackgroundManager {
41762
41920
  const directChildren = this.getTasksByParentSession(sessionID);
41763
41921
  for (const child of directChildren) {
41764
41922
  result.push(child);
41765
- const descendants = this.getAllDescendantTasks(child.sessionID);
41766
- result.push(...descendants);
41923
+ if (child.sessionID) {
41924
+ const descendants = this.getAllDescendantTasks(child.sessionID);
41925
+ result.push(...descendants);
41926
+ }
41767
41927
  }
41768
41928
  return result;
41769
41929
  }
@@ -41775,6 +41935,12 @@ class BackgroundManager {
41775
41935
  }
41776
41936
  return;
41777
41937
  }
41938
+ getConcurrencyKeyFromInput(input) {
41939
+ if (input.model) {
41940
+ return `${input.model.providerID}/${input.model.modelID}`;
41941
+ }
41942
+ return input.agent;
41943
+ }
41778
41944
  async trackTask(input) {
41779
41945
  const existingTask = this.tasks.get(input.taskId);
41780
41946
  if (existingTask) {
@@ -41789,12 +41955,14 @@ class BackgroundManager {
41789
41955
  if (!existingTask.concurrencyGroup) {
41790
41956
  existingTask.concurrencyGroup = input.concurrencyKey ?? existingTask.agent;
41791
41957
  }
41792
- subagentSessions.add(existingTask.sessionID);
41958
+ if (existingTask.sessionID) {
41959
+ subagentSessions.add(existingTask.sessionID);
41960
+ }
41793
41961
  this.startPolling();
41794
- if (existingTask.status === "running") {
41795
- const pending2 = this.pendingByParent.get(input.parentSessionID) ?? new Set;
41796
- pending2.add(existingTask.id);
41797
- this.pendingByParent.set(input.parentSessionID, pending2);
41962
+ if (existingTask.status === "pending" || existingTask.status === "running") {
41963
+ const pending = this.pendingByParent.get(input.parentSessionID) ?? new Set;
41964
+ pending.add(existingTask.id);
41965
+ this.pendingByParent.set(input.parentSessionID, pending);
41798
41966
  } else if (!parentChanged) {
41799
41967
  this.cleanupPendingByParent(existingTask);
41800
41968
  }
@@ -41826,9 +41994,11 @@ class BackgroundManager {
41826
41994
  this.tasks.set(task.id, task);
41827
41995
  subagentSessions.add(input.sessionID);
41828
41996
  this.startPolling();
41829
- const pending = this.pendingByParent.get(input.parentSessionID) ?? new Set;
41830
- pending.add(task.id);
41831
- this.pendingByParent.set(input.parentSessionID, pending);
41997
+ if (input.parentSessionID) {
41998
+ const pending = this.pendingByParent.get(input.parentSessionID) ?? new Set;
41999
+ pending.add(task.id);
42000
+ this.pendingByParent.set(input.parentSessionID, pending);
42001
+ }
41832
42002
  log("[background-agent] Registered external task:", { taskId: task.id, sessionID: input.sessionID });
41833
42003
  return task;
41834
42004
  }
@@ -41837,6 +42007,9 @@ class BackgroundManager {
41837
42007
  if (!existingTask) {
41838
42008
  throw new Error(`Task not found for session: ${input.sessionId}`);
41839
42009
  }
42010
+ if (!existingTask.sessionID) {
42011
+ throw new Error(`Task has no sessionID: ${existingTask.id}`);
42012
+ }
41840
42013
  if (existingTask.status === "running") {
41841
42014
  log("[background-agent] Resume skipped - task already running:", {
41842
42015
  taskId: existingTask.id,
@@ -41861,10 +42034,14 @@ class BackgroundManager {
41861
42034
  lastUpdate: new Date
41862
42035
  };
41863
42036
  this.startPolling();
41864
- subagentSessions.add(existingTask.sessionID);
41865
- const pending = this.pendingByParent.get(input.parentSessionID) ?? new Set;
41866
- pending.add(existingTask.id);
41867
- this.pendingByParent.set(input.parentSessionID, pending);
42037
+ if (existingTask.sessionID) {
42038
+ subagentSessions.add(existingTask.sessionID);
42039
+ }
42040
+ if (input.parentSessionID) {
42041
+ const pending = this.pendingByParent.get(input.parentSessionID) ?? new Set;
42042
+ pending.add(existingTask.id);
42043
+ this.pendingByParent.set(input.parentSessionID, pending);
42044
+ }
41868
42045
  const toastManager = getTaskToastManager();
41869
42046
  if (toastManager) {
41870
42047
  toastManager.addTask({
@@ -41956,7 +42133,10 @@ class BackgroundManager {
41956
42133
  const task = this.findBySession(sessionID);
41957
42134
  if (!task || task.status !== "running")
41958
42135
  return;
41959
- const elapsedMs = Date.now() - task.startedAt.getTime();
42136
+ const startedAt = task.startedAt;
42137
+ if (!startedAt)
42138
+ return;
42139
+ const elapsedMs = Date.now() - startedAt.getTime();
41960
42140
  const MIN_IDLE_TIME_MS = 5000;
41961
42141
  if (elapsedMs < MIN_IDLE_TIME_MS) {
41962
42142
  log("[background-agent] Ignoring early session.idle, elapsed:", { elapsedMs, taskId: task.id });
@@ -42067,6 +42247,28 @@ class BackgroundManager {
42067
42247
  }
42068
42248
  }
42069
42249
  }
42250
+ cancelPendingTask(taskId) {
42251
+ const task = this.tasks.get(taskId);
42252
+ if (!task || task.status !== "pending") {
42253
+ return false;
42254
+ }
42255
+ const key = task.model ? `${task.model.providerID}/${task.model.modelID}` : task.agent;
42256
+ const queue = this.queuesByKey.get(key);
42257
+ if (queue) {
42258
+ const index = queue.findIndex((item) => item.task.id === taskId);
42259
+ if (index !== -1) {
42260
+ queue.splice(index, 1);
42261
+ if (queue.length === 0) {
42262
+ this.queuesByKey.delete(key);
42263
+ }
42264
+ }
42265
+ }
42266
+ task.status = "cancelled";
42267
+ task.completedAt = new Date;
42268
+ this.cleanupPendingByParent(task);
42269
+ log("[background-agent] Cancelled pending task:", { taskId, key });
42270
+ return true;
42271
+ }
42070
42272
  startPolling() {
42071
42273
  if (this.pollingInterval)
42072
42274
  return;
@@ -42144,7 +42346,7 @@ class BackgroundManager {
42144
42346
  return true;
42145
42347
  }
42146
42348
  async notifyParentSession(task) {
42147
- const duration3 = this.formatDuration(task.startedAt, task.completedAt);
42349
+ const duration3 = this.formatDuration(task.startedAt ?? new Date, task.completedAt);
42148
42350
  log("[background-agent] notifyParentSession called for task:", task.id);
42149
42351
  const toastManager = getTaskToastManager();
42150
42352
  if (toastManager) {
@@ -42163,12 +42365,12 @@ class BackgroundManager {
42163
42365
  }
42164
42366
  const allComplete = !pendingSet || pendingSet.size === 0;
42165
42367
  const remainingCount = pendingSet?.size ?? 0;
42166
- const statusText = task.status === "error" ? "FAILED" : "COMPLETED";
42368
+ const statusText = task.status === "completed" ? "COMPLETED" : "CANCELLED";
42167
42369
  const errorInfo = task.error ? `
42168
42370
  **Error:** ${task.error}` : "";
42169
42371
  let notification;
42170
42372
  if (allComplete) {
42171
- const completedTasks = Array.from(this.tasks.values()).filter((t) => t.parentSessionID === task.parentSessionID && t.status !== "running").map((t) => `- \`${t.id}\`: ${t.description}`).join(`
42373
+ const completedTasks = Array.from(this.tasks.values()).filter((t) => t.parentSessionID === task.parentSessionID && t.status !== "running" && t.status !== "pending").map((t) => `- \`${t.id}\`: ${t.description}`).join(`
42172
42374
  `);
42173
42375
  notification = `<system-reminder>
42174
42376
  [ALL BACKGROUND TASKS COMPLETE]
@@ -42264,11 +42466,16 @@ Use \`background_output(task_id="${task.id}")\` to retrieve this result when rea
42264
42466
  pruneStaleTasksAndNotifications() {
42265
42467
  const now = Date.now();
42266
42468
  for (const [taskId, task] of this.tasks.entries()) {
42267
- const age = now - task.startedAt.getTime();
42469
+ const timestamp2 = task.status === "pending" ? task.queuedAt?.getTime() : task.startedAt?.getTime();
42470
+ if (!timestamp2) {
42471
+ continue;
42472
+ }
42473
+ const age = now - timestamp2;
42268
42474
  if (age > TASK_TTL_MS) {
42269
- log("[background-agent] Pruning stale task:", { taskId, age: Math.round(age / 1000) + "s" });
42475
+ const errorMessage = task.status === "pending" ? "Task timed out while queued (30 minutes)" : "Task timed out after 30 minutes";
42476
+ log("[background-agent] Pruning stale task:", { taskId, status: task.status, age: Math.round(age / 1000) + "s" });
42270
42477
  task.status = "error";
42271
- task.error = "Task timed out after 30 minutes";
42478
+ task.error = errorMessage;
42272
42479
  task.completedAt = new Date;
42273
42480
  if (task.concurrencyKey) {
42274
42481
  this.concurrencyManager.release(task.concurrencyKey);
@@ -42277,7 +42484,9 @@ Use \`background_output(task_id="${task.id}")\` to retrieve this result when rea
42277
42484
  this.cleanupPendingByParent(task);
42278
42485
  this.clearNotificationsForTask(taskId);
42279
42486
  this.tasks.delete(taskId);
42280
- subagentSessions.delete(task.sessionID);
42487
+ if (task.sessionID) {
42488
+ subagentSessions.delete(task.sessionID);
42489
+ }
42281
42490
  }
42282
42491
  }
42283
42492
  for (const [sessionID, notifications] of this.notifications.entries()) {
@@ -42286,6 +42495,8 @@ Use \`background_output(task_id="${task.id}")\` to retrieve this result when rea
42286
42495
  continue;
42287
42496
  }
42288
42497
  const validNotifications = notifications.filter((task) => {
42498
+ if (!task.startedAt)
42499
+ return false;
42289
42500
  const age = now - task.startedAt.getTime();
42290
42501
  return age <= TASK_TTL_MS;
42291
42502
  });
@@ -42304,7 +42515,11 @@ Use \`background_output(task_id="${task.id}")\` to retrieve this result when rea
42304
42515
  continue;
42305
42516
  if (!task.progress?.lastUpdate)
42306
42517
  continue;
42307
- const runtime = now - task.startedAt.getTime();
42518
+ const startedAt = task.startedAt;
42519
+ const sessionID = task.sessionID;
42520
+ if (!startedAt || !sessionID)
42521
+ continue;
42522
+ const runtime = now - startedAt.getTime();
42308
42523
  if (runtime < MIN_RUNTIME_BEFORE_STALE_MS)
42309
42524
  continue;
42310
42525
  const timeSinceLastUpdate = now - task.progress.lastUpdate.getTime();
@@ -42321,7 +42536,7 @@ Use \`background_output(task_id="${task.id}")\` to retrieve this result when rea
42321
42536
  task.concurrencyKey = undefined;
42322
42537
  }
42323
42538
  this.client.session.abort({
42324
- path: { id: task.sessionID }
42539
+ path: { id: sessionID }
42325
42540
  }).catch(() => {});
42326
42541
  log(`[background-agent] Task ${task.id} interrupted: stale timeout`);
42327
42542
  try {
@@ -42339,17 +42554,20 @@ Use \`background_output(task_id="${task.id}")\` to retrieve this result when rea
42339
42554
  for (const task of this.tasks.values()) {
42340
42555
  if (task.status !== "running")
42341
42556
  continue;
42557
+ const sessionID = task.sessionID;
42558
+ if (!sessionID)
42559
+ continue;
42342
42560
  try {
42343
- const sessionStatus = allStatuses[task.sessionID];
42561
+ const sessionStatus = allStatuses[sessionID];
42344
42562
  if (sessionStatus?.type === "idle") {
42345
- const hasValidOutput = await this.validateSessionHasOutput(task.sessionID);
42563
+ const hasValidOutput = await this.validateSessionHasOutput(sessionID);
42346
42564
  if (!hasValidOutput) {
42347
42565
  log("[background-agent] Polling idle but no valid output yet, waiting:", task.id);
42348
42566
  continue;
42349
42567
  }
42350
42568
  if (task.status !== "running")
42351
42569
  continue;
42352
- const hasIncompleteTodos2 = await this.checkSessionTodos(task.sessionID);
42570
+ const hasIncompleteTodos2 = await this.checkSessionTodos(sessionID);
42353
42571
  if (hasIncompleteTodos2) {
42354
42572
  log("[background-agent] Task has incomplete todos via polling, waiting:", task.id);
42355
42573
  continue;
@@ -42358,7 +42576,7 @@ Use \`background_output(task_id="${task.id}")\` to retrieve this result when rea
42358
42576
  continue;
42359
42577
  }
42360
42578
  const messagesResult = await this.client.session.messages({
42361
- path: { id: task.sessionID }
42579
+ path: { id: sessionID }
42362
42580
  });
42363
42581
  if (!messagesResult.error && messagesResult.data) {
42364
42582
  const messages = messagesResult.data;
@@ -42389,19 +42607,33 @@ Use \`background_output(task_id="${task.id}")\` to retrieve this result when rea
42389
42607
  task.progress.lastMessageAt = new Date;
42390
42608
  }
42391
42609
  const currentMsgCount = messages.length;
42392
- const elapsedMs = Date.now() - task.startedAt.getTime();
42610
+ const startedAt = task.startedAt;
42611
+ if (!startedAt)
42612
+ continue;
42613
+ const elapsedMs = Date.now() - startedAt.getTime();
42393
42614
  if (elapsedMs >= MIN_STABILITY_TIME_MS) {
42394
42615
  if (task.lastMsgCount === currentMsgCount) {
42395
42616
  task.stablePolls = (task.stablePolls ?? 0) + 1;
42396
42617
  if (task.stablePolls >= 3) {
42397
- const hasValidOutput = await this.validateSessionHasOutput(task.sessionID);
42618
+ const recheckStatus = await this.client.session.status();
42619
+ const recheckData = recheckStatus.data ?? {};
42620
+ const currentStatus = recheckData[sessionID];
42621
+ if (currentStatus?.type !== "idle") {
42622
+ log("[background-agent] Stability reached but session not idle, resetting:", {
42623
+ taskId: task.id,
42624
+ sessionStatus: currentStatus?.type ?? "not_in_status"
42625
+ });
42626
+ task.stablePolls = 0;
42627
+ continue;
42628
+ }
42629
+ const hasValidOutput = await this.validateSessionHasOutput(sessionID);
42398
42630
  if (!hasValidOutput) {
42399
42631
  log("[background-agent] Stability reached but no valid output, waiting:", task.id);
42400
42632
  continue;
42401
42633
  }
42402
42634
  if (task.status !== "running")
42403
42635
  continue;
42404
- const hasIncompleteTodos2 = await this.checkSessionTodos(task.sessionID);
42636
+ const hasIncompleteTodos2 = await this.checkSessionTodos(sessionID);
42405
42637
  if (!hasIncompleteTodos2) {
42406
42638
  await this.tryCompleteTask(task, "stability detection");
42407
42639
  continue;
@@ -42437,6 +42669,8 @@ Use \`background_output(task_id="${task.id}")\` to retrieve this result when rea
42437
42669
  this.tasks.clear();
42438
42670
  this.notifications.clear();
42439
42671
  this.pendingByParent.clear();
42672
+ this.queuesByKey.clear();
42673
+ this.processingKeys.clear();
42440
42674
  this.unregisterProcessCleanup();
42441
42675
  log("[background-agent] Shutdown complete");
42442
42676
  }