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/README.ja.md +35 -802
- package/README.md +43 -929
- package/README.zh-cn.md +35 -925
- package/dist/agents/sisyphus-junior.d.ts +1 -2
- package/dist/cli/index.js +8 -8
- package/dist/features/background-agent/manager.d.ts +10 -0
- package/dist/features/background-agent/types.d.ts +4 -3
- package/dist/index.js +323 -89
- package/package.json +8 -8
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
|
-
|
|
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 === "
|
|
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
|
-
|
|
|
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
|
|
40253
|
-
if (
|
|
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
|
|
40258
|
-
|
|
40259
|
-
|
|
40260
|
-
|
|
40261
|
-
task2.
|
|
40262
|
-
|
|
40263
|
-
|
|
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 ${
|
|
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
|
-
|
|
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
|
|
41640
|
-
|
|
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
|
-
|
|
41668
|
-
|
|
41669
|
-
|
|
41670
|
-
|
|
41671
|
-
|
|
41672
|
-
|
|
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
|
-
|
|
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.
|
|
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
|
-
|
|
41766
|
-
|
|
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
|
-
|
|
41958
|
+
if (existingTask.sessionID) {
|
|
41959
|
+
subagentSessions.add(existingTask.sessionID);
|
|
41960
|
+
}
|
|
41793
41961
|
this.startPolling();
|
|
41794
|
-
if (existingTask.status === "running") {
|
|
41795
|
-
const
|
|
41796
|
-
|
|
41797
|
-
this.pendingByParent.set(input.parentSessionID,
|
|
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
|
-
|
|
41830
|
-
|
|
41831
|
-
|
|
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
|
-
|
|
41865
|
-
|
|
41866
|
-
|
|
41867
|
-
|
|
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
|
|
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 === "
|
|
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
|
|
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
|
-
|
|
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 =
|
|
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
|
-
|
|
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
|
|
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:
|
|
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[
|
|
42561
|
+
const sessionStatus = allStatuses[sessionID];
|
|
42344
42562
|
if (sessionStatus?.type === "idle") {
|
|
42345
|
-
const hasValidOutput = await this.validateSessionHasOutput(
|
|
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(
|
|
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:
|
|
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
|
|
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
|
|
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(
|
|
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
|
}
|