readme cleanup
This commit is contained in:
@@ -54,7 +54,7 @@ Task IDs are zero-padded strings (`"01"`, `"02"`, etc.). The parser prepends `0`
|
|||||||
|
|
||||||
## Command routing
|
## Command routing
|
||||||
|
|
||||||
`/ralpi` with no args → plan. First token looks like a path (`@path`, `./path`, `.md`, etc.) → run. Otherwise dispatches to subcommand (`run`, `plan`, `status`, `resume`, `next`, `reset`).
|
`/ralpi` with no args → plan. First token looks like a path (`@path`, `./path`, `.md`, etc.) → run. Otherwise dispatches to subcommand (`run`, `plan`, `resume`, `reset`).
|
||||||
|
|
||||||
## Config
|
## Config
|
||||||
|
|
||||||
|
|||||||
117
README.md
117
README.md
@@ -4,75 +4,33 @@ Execute tasks from task files using DAG-based dependency resolution with persist
|
|||||||
|
|
||||||
## Features
|
## Features
|
||||||
|
|
||||||
- **DAG-based execution**: Tasks are ordered by dependencies using Kahn's algorithm
|
|
||||||
- **Parallel batching**: Independent tasks in each batch can run concurrently
|
- **Parallel batching**: Independent tasks in each batch can run concurrently
|
||||||
- **Persistent progress**: Execution state saved to `.ralpi/progress.json`
|
- **Persistent progress**: Execution state saved to `.ralpi/progress.json`
|
||||||
- **Reflection system**: Each task produces a reflection for downstream tasks
|
- **Reflection system**: Each task produces a reflection for downstream tasks
|
||||||
- **Retry with backoff**: Failed tasks retry with exponential backoff
|
- **Retry with backoff**: Failed tasks retry with exponential backoff
|
||||||
- **Multiple formats**: Supports Fio README, simple checkboxes, and YAML
|
- **Multiple formats**: Supports simple checkboxes, and YAML
|
||||||
- **Chat progress**: Real-time progress messages in Pi chat via `pi.sendMessage`
|
|
||||||
- **Tool usage tracking**: Detects and reports tool usage (read, write, edit, bash) from task execution
|
- **Tool usage tracking**: Detects and reports tool usage (read, write, edit, bash) from task execution
|
||||||
- **Git commit capture**: Captures git commit messages and generates summaries per task
|
|
||||||
- **Configurable timeouts**: Task-level timeouts via meta blocks, with global fallback
|
- **Configurable timeouts**: Task-level timeouts via meta blocks, with global fallback
|
||||||
- **Session saving**: Saves full task output for expandable session review
|
- **Session saving**: Saves full task output for expandable session review
|
||||||
- **Resume auto-discovery**: Automatically finds and resumes interrupted execution
|
- **Resume auto-discovery**: Automatically finds and resumes interrupted execution
|
||||||
- **Custom message renderer**: Compact UI labels with expandable details in Pi TUI
|
|
||||||
|
|
||||||
## Usage
|
## Usage
|
||||||
|
|
||||||
```
|
```
|
||||||
/ralpi plan [task-file] # Show execution plan
|
/ralpi [task-file] # Execute all tasks
|
||||||
/ralpi run [task-file] # Execute all tasks
|
/ralpi plan # Alias to /task-manager to plan new tasks
|
||||||
/ralpi status [task-file] # Show current progress
|
/ralpi resume # Resume paused execution
|
||||||
/ralpi resume [task-file] # Resume paused execution
|
/ralpi reset [task-file] # Reset progress and .ralpi directory - does not modify PRD
|
||||||
/ralpi next [task-file] # Execute next batch only
|
|
||||||
/ralpi reset [task-file] # Reset all progress
|
|
||||||
```
|
```
|
||||||
|
|
||||||
## Task File Formats
|
## Task File Formats
|
||||||
|
|
||||||
### Fio README Format
|
### Highly recommended to use the task-manager prompt for prd construction, it's output pairs perfectly
|
||||||
|
|
||||||
```markdown
|
|
||||||
# Project Title
|
# Project Title
|
||||||
|
|
||||||
## Tasks
|
## Tasks
|
||||||
|
|
||||||
- [ ] 01 — Setup project structure -> `tasks/01-setup.md`
|
|
||||||
- [ ] 02 — Implement auth -> `tasks/02-auth.md`
|
|
||||||
- [ ] 03 — Build API -> `tasks/03-api.md`
|
|
||||||
|
|
||||||
## Dependencies
|
|
||||||
|
|
||||||
1 -> 2,3
|
|
||||||
2 -> 3
|
|
||||||
```
|
|
||||||
|
|
||||||
#### Supported Dependency Formats
|
|
||||||
|
|
||||||
The parser supports two dependency declaration styles in the `## Dependencies` section:
|
|
||||||
|
|
||||||
**Arrow Notation** (recommended):
|
|
||||||
```
|
|
||||||
1 -> 2,3,4
|
|
||||||
5 -> 6
|
|
||||||
```
|
|
||||||
This means: "Task 1 must complete before tasks 2, 3, and 4 can start."
|
|
||||||
|
|
||||||
**Natural Language**:
|
|
||||||
```
|
|
||||||
13 depends on 17, 18, 19, 20
|
|
||||||
14 depends on 13, 15, 16
|
|
||||||
```
|
|
||||||
This means: "Task 13 depends on tasks 17, 18, 19, and 20."
|
|
||||||
|
|
||||||
**Parallel Groups** (informational only):
|
|
||||||
```
|
|
||||||
1, 2, 3, 4 can be done in parallel
|
|
||||||
5, 6, 7, 8 can be done in parallel
|
|
||||||
```
|
|
||||||
Note: These lines are ignored by the parser. Use explicit dependencies to control execution order.
|
|
||||||
|
|
||||||
### Simple Checkbox Format
|
### Simple Checkbox Format
|
||||||
|
|
||||||
```markdown
|
```markdown
|
||||||
@@ -96,9 +54,45 @@ tasks:
|
|||||||
depends_on: ["01"]
|
depends_on: ["01"]
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## Dependencies
|
||||||
|
|
||||||
|
### Arrow Notation (recommended):
|
||||||
|
|
||||||
|
1 -> 2,3,4
|
||||||
|
5 -> 6
|
||||||
|
This means: "Task 1 must complete before tasks 2, 3, and 4 can start."
|
||||||
|
|
||||||
|
### Natural Language:
|
||||||
|
|
||||||
|
13 depends on 17, 18, 19, 20
|
||||||
|
14 depends on 13, 15, 16
|
||||||
|
|
||||||
|
This means: "Task 13 depends on tasks 17, 18, 19, and 20."
|
||||||
|
|
||||||
|
### Parallel Groups (informational only):
|
||||||
|
|
||||||
|
1, 2, 3, 4 can be done in parallel
|
||||||
|
5, 6, 7, 8 can be done in parallel
|
||||||
|
|
||||||
|
Note: These lines are ignored by the parser. Use explicit dependencies to control execution order.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
## Configuration
|
## Configuration
|
||||||
|
|
||||||
Create config files. Both are optional:
|
### Task-Level Timeout
|
||||||
|
|
||||||
|
You can set a timeout for individual tasks using a meta block in the task file:
|
||||||
|
|
||||||
|
```markdown
|
||||||
|
- [ ] 01: Setup project structure
|
||||||
|
timeout: 10m
|
||||||
|
```
|
||||||
|
|
||||||
|
Supported formats: `10m` (minutes), `600s` (seconds), `3600000` (milliseconds)
|
||||||
|
|
||||||
|
|
||||||
|
### Config files
|
||||||
|
|
||||||
| Scope | Path |
|
| Scope | Path |
|
||||||
|-------|------|
|
|-------|------|
|
||||||
@@ -115,9 +109,6 @@ prompts:
|
|||||||
projectContext: "Additional context for all tasks"
|
projectContext: "Additional context for all tasks"
|
||||||
```
|
```
|
||||||
|
|
||||||
> ralpi deliberately does **not** set timeouts or retries — those are inherited
|
|
||||||
> from Pi's own settings. Tasks run until they complete or Pi's own flow stops them.
|
|
||||||
>
|
|
||||||
> `execution.models` uses slot-aware round-robin: with 3 models and 2 concurrent
|
> `execution.models` uses slot-aware round-robin: with 3 models and 2 concurrent
|
||||||
> tasks, only the first two models are used. The third model is only touched when
|
> tasks, only the first two models are used. The third model is only touched when
|
||||||
> a third concurrent task starts. Freed model slots are reused before new ones
|
> a third concurrent task starts. Freed model slots are reused before new ones
|
||||||
@@ -127,28 +118,6 @@ prompts:
|
|||||||
> the task automatically cycles to the next model in the list without counting it
|
> the task automatically cycles to the next model in the list without counting it
|
||||||
> as a task failure. Each model is tried once before the task is marked as failed.
|
> as a task failure. Each model is tried once before the task is marked as failed.
|
||||||
|
|
||||||
The keys mirror the nested structure of `RalpiConfig` in `src/types.ts`.
|
|
||||||
|
|
||||||
### Precedence (highest wins)
|
|
||||||
|
|
||||||
| Priority | Source |
|
|
||||||
|----------|--------|
|
|
||||||
| **1st** | In-memory overrides (`model`, `thinkingLevel` from parent Pi session) |
|
|
||||||
| **2nd** | `./.ralpi/config.yaml` — project-level |
|
|
||||||
| **3rd** | `~/.pi/ralpi/config.yaml` — global, shared across projects |
|
|
||||||
| **4th** | `DEFAULT_CONFIG` in `src/types.ts` |
|
|
||||||
|
|
||||||
### Task-Level Timeout
|
|
||||||
|
|
||||||
You can set a timeout for individual tasks using a meta block in the task file:
|
|
||||||
|
|
||||||
```markdown
|
|
||||||
- [ ] 01: Setup project structure
|
|
||||||
timeout: 10m
|
|
||||||
```
|
|
||||||
|
|
||||||
Supported formats: `10m` (minutes), `600s` (seconds), `3600000` (milliseconds)
|
|
||||||
|
|
||||||
## State Files
|
## State Files
|
||||||
|
|
||||||
- `.ralpi/progress.json` - Execution progress
|
- `.ralpi/progress.json` - Execution progress
|
||||||
|
|||||||
144
index.ts
144
index.ts
@@ -10,8 +10,6 @@ import {
|
|||||||
buildExecutionPlan,
|
buildExecutionPlan,
|
||||||
buildSequentialPlan,
|
buildSequentialPlan,
|
||||||
formatExecutionPlan,
|
formatExecutionPlan,
|
||||||
getReadyTasks,
|
|
||||||
getBlockedTasks,
|
|
||||||
} from "./src/dag";
|
} from "./src/dag";
|
||||||
import { ProgressTracker } from "./src/progress";
|
import { ProgressTracker } from "./src/progress";
|
||||||
import { buildPlanPrompt } from "./src/prompts";
|
import { buildPlanPrompt } from "./src/prompts";
|
||||||
@@ -21,11 +19,10 @@ import {
|
|||||||
loadConfig,
|
loadConfig,
|
||||||
resolveTaskArg,
|
resolveTaskArg,
|
||||||
formatProgressStatus,
|
formatProgressStatus,
|
||||||
formatAllPRDsStatus,
|
|
||||||
findProgressFile,
|
findProgressFile,
|
||||||
} from "./src/utils";
|
} from "./src/utils";
|
||||||
|
|
||||||
const COMMANDS = ["status", "resume", "next", "reset"] as const;
|
const COMMANDS = ["plan", "resume", "reset"] as const;
|
||||||
|
|
||||||
type ExecutionMode = "parallel" | "sequential";
|
type ExecutionMode = "parallel" | "sequential";
|
||||||
|
|
||||||
@@ -320,9 +317,9 @@ export default function ralpiLoopExtension(pi: ExtensionAPI): void {
|
|||||||
pi.getThinkingLevel(),
|
pi.getThinkingLevel(),
|
||||||
);
|
);
|
||||||
case "plan":
|
case "plan":
|
||||||
return handlePlan(ctx, parts.slice(1));
|
pi.sendUserMessage("@task-manager");
|
||||||
case "status":
|
ctx.ui.notify("Opening Task Manager...", "info");
|
||||||
return handleStatus(ctx, parts.slice(1));
|
return;
|
||||||
case "resume":
|
case "resume":
|
||||||
return handleResume(
|
return handleResume(
|
||||||
ctx,
|
ctx,
|
||||||
@@ -331,14 +328,6 @@ export default function ralpiLoopExtension(pi: ExtensionAPI): void {
|
|||||||
ctx.model,
|
ctx.model,
|
||||||
pi.getThinkingLevel(),
|
pi.getThinkingLevel(),
|
||||||
);
|
);
|
||||||
case "next":
|
|
||||||
return handleNext(
|
|
||||||
ctx,
|
|
||||||
parts.slice(1),
|
|
||||||
sendProgress,
|
|
||||||
ctx.model,
|
|
||||||
pi.getThinkingLevel(),
|
|
||||||
);
|
|
||||||
case "reset":
|
case "reset":
|
||||||
return handleReset(ctx, parts.slice(1));
|
return handleReset(ctx, parts.slice(1));
|
||||||
default: {
|
default: {
|
||||||
@@ -473,46 +462,7 @@ async function handleRun(
|
|||||||
}
|
}
|
||||||
|
|
||||||
// ─── /ralpi status ───────────────────────────────────────────────────────────
|
// ─── /ralpi status ───────────────────────────────────────────────────────────
|
||||||
|
// (removed — use /ralpi plan to invoke @task-manager)
|
||||||
async function handleStatus(
|
|
||||||
ctx: ExtensionContext,
|
|
||||||
args: string[],
|
|
||||||
): Promise<void> {
|
|
||||||
if (args[0]) {
|
|
||||||
const taskFile = resolveTaskArg(args[0], process.cwd());
|
|
||||||
const existingProgress = findProgressFile(process.cwd(), taskFile);
|
|
||||||
if (existingProgress) {
|
|
||||||
const projectDir = path.dirname(path.dirname(existingProgress.path));
|
|
||||||
const progress = new ProgressTracker(
|
|
||||||
projectDir,
|
|
||||||
taskFile,
|
|
||||||
existingProgress.prdKey,
|
|
||||||
);
|
|
||||||
ctx.ui.notify(formatProgressStatus(progress.getState()), "info");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
// No progress yet for this task — parse and show plan instead
|
|
||||||
const project = parseTaskFile(taskFile);
|
|
||||||
ctx.ui.notify(
|
|
||||||
`No progress for ${path.basename(taskFile)}. ${
|
|
||||||
project.tasks.length
|
|
||||||
} tasks found.\nUse /ralpi run ${args[0]} to start.`,
|
|
||||||
"info",
|
|
||||||
);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const found = findProgressFile(process.cwd());
|
|
||||||
if (!found) {
|
|
||||||
ctx.ui.notify(
|
|
||||||
"No .ralpi/progress.json found. Start with /ralpi run [task-file]",
|
|
||||||
"warning",
|
|
||||||
);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
ctx.ui.notify(formatAllPRDsStatus(found.state), "info");
|
|
||||||
}
|
|
||||||
|
|
||||||
// ─── /ralpi resume ───────────────────────────────────────────────────────────
|
// ─── /ralpi resume ───────────────────────────────────────────────────────────
|
||||||
|
|
||||||
@@ -587,89 +537,7 @@ async function handleResume(
|
|||||||
}
|
}
|
||||||
|
|
||||||
// ─── /ralpi next ─────────────────────────────────────────────────────────────
|
// ─── /ralpi next ─────────────────────────────────────────────────────────────
|
||||||
|
// (removed — use /ralpi run to execute tasks)
|
||||||
async function handleNext(
|
|
||||||
ctx: ExtensionContext,
|
|
||||||
args: string[],
|
|
||||||
sendChatMessage?: SendChatMessage,
|
|
||||||
parentModel?: unknown,
|
|
||||||
parentThinkingLevel?: unknown,
|
|
||||||
): Promise<void> {
|
|
||||||
let taskFile: string;
|
|
||||||
let projectDir: string;
|
|
||||||
let found: ReturnType<typeof findProgressFile>;
|
|
||||||
|
|
||||||
if (args[0]) {
|
|
||||||
taskFile = resolveTaskArg(args[0], process.cwd());
|
|
||||||
found = findProgressFile(process.cwd(), taskFile);
|
|
||||||
if (found) {
|
|
||||||
projectDir = path.dirname(path.dirname(found.path));
|
|
||||||
} else {
|
|
||||||
projectDir = process.cwd();
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
found = findProgressFile(process.cwd());
|
|
||||||
if (!found) {
|
|
||||||
ctx.ui.notify(
|
|
||||||
"No .ralpi/progress.json found. Start with /ralpi run [task-file]",
|
|
||||||
"warning",
|
|
||||||
);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
taskFile = found.state.prds
|
|
||||||
? Object.values(found.state.prds)[0].sourcePath
|
|
||||||
: found.state.sourcePath;
|
|
||||||
projectDir = path.dirname(path.dirname(found.path));
|
|
||||||
}
|
|
||||||
|
|
||||||
const project = parseTaskFile(taskFile);
|
|
||||||
if (!Array.isArray(project.tasks)) {
|
|
||||||
throw new Error(
|
|
||||||
`Parsed project from ${taskFile} has invalid tasks: expected array, got ${typeof project.tasks}`,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
const config = loadConfig(projectDir);
|
|
||||||
config.model = parentModel ?? ctx.model;
|
|
||||||
config.thinkingLevel = parentThinkingLevel;
|
|
||||||
const progress = new ProgressTracker(projectDir, taskFile, found?.prdKey);
|
|
||||||
|
|
||||||
const completed = buildCompletedSet(progress, project);
|
|
||||||
const ready = getReadyTasks(project, completed);
|
|
||||||
|
|
||||||
if (ready.length === 0) {
|
|
||||||
ctx.ui.notify(
|
|
||||||
"No tasks ready to execute. All tasks completed or blocked.",
|
|
||||||
"info",
|
|
||||||
);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const nextBatch = ready.slice(
|
|
||||||
0,
|
|
||||||
config.execution.maxParallel || ready.length,
|
|
||||||
);
|
|
||||||
|
|
||||||
for (const task of nextBatch) {
|
|
||||||
await executeBatch(
|
|
||||||
[task],
|
|
||||||
project,
|
|
||||||
config,
|
|
||||||
progress,
|
|
||||||
ctx,
|
|
||||||
{ parallel: false },
|
|
||||||
sendChatMessage,
|
|
||||||
projectDir,
|
|
||||||
);
|
|
||||||
updateTaskInFile(taskFile, task.id, progress.getTaskStatus(task.id));
|
|
||||||
}
|
|
||||||
|
|
||||||
ctx.ui.notify(
|
|
||||||
`Executed: ${nextBatch
|
|
||||||
.map((t) => t.id)
|
|
||||||
.join(", ")}\n\n${formatProgressStatus(progress.getState())}`,
|
|
||||||
"info",
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
// ─── /ralpi reset ────────────────────────────────────────────────────────────
|
// ─── /ralpi reset ────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
|||||||
10
package.json
10
package.json
@@ -8,7 +8,7 @@
|
|||||||
"task-runner",
|
"task-runner",
|
||||||
"dag",
|
"dag",
|
||||||
"task-manager",
|
"task-manager",
|
||||||
"ralpi-loop",
|
"ralph-loop",
|
||||||
"prd"
|
"prd"
|
||||||
],
|
],
|
||||||
"author": "Michael Freno",
|
"author": "Michael Freno",
|
||||||
@@ -54,14 +54,6 @@
|
|||||||
"@earendil-works/pi-coding-agent": "*",
|
"@earendil-works/pi-coding-agent": "*",
|
||||||
"@earendil-works/pi-tui": "*"
|
"@earendil-works/pi-tui": "*"
|
||||||
},
|
},
|
||||||
"peerDependenciesMeta": {
|
|
||||||
"@earendil-works/pi-coding-agent": {
|
|
||||||
"optional": true
|
|
||||||
},
|
|
||||||
"@earendil-works/pi-tui": {
|
|
||||||
"optional": true
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"publishConfig": {
|
"publishConfig": {
|
||||||
"access": "public"
|
"access": "public"
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -296,8 +296,8 @@ export async function runTask(
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (!output.success) {
|
if (!output.success) {
|
||||||
sendChatMessage?.(`✗ ${taskHeader} — ${output.error}`);
|
// Failure reporting is handled by the caller (executeTask) to avoid
|
||||||
ctx.ui.notify(`Task ${task.id} failed: ${output.error}`, "error");
|
// duplicate messages when model failover or retry cycling is active.
|
||||||
return {
|
return {
|
||||||
success: false,
|
success: false,
|
||||||
error: output.error,
|
error: output.error,
|
||||||
@@ -433,6 +433,7 @@ export async function executeBatch(
|
|||||||
roundRobin?.release(task.id);
|
roundRobin?.release(task.id);
|
||||||
const errorMsg = error instanceof Error ? error.message : String(error);
|
const errorMsg = error instanceof Error ? error.message : String(error);
|
||||||
progress.markFailed(task.id, errorMsg);
|
progress.markFailed(task.id, errorMsg);
|
||||||
|
sendChatMessage?.(`✗ ${task.id} · ${task.title} — ${errorMsg}`);
|
||||||
ctx.ui.notify(`Task ${task.id} failed: ${errorMsg}`, "error");
|
ctx.ui.notify(`Task ${task.id} failed: ${errorMsg}`, "error");
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@@ -555,6 +556,7 @@ async function executeBatchParallel(
|
|||||||
roundRobin?.release(task.id);
|
roundRobin?.release(task.id);
|
||||||
const errorMsg = error instanceof Error ? error.message : String(error);
|
const errorMsg = error instanceof Error ? error.message : String(error);
|
||||||
progress.markFailed(task.id, errorMsg);
|
progress.markFailed(task.id, errorMsg);
|
||||||
|
sendChatMessage?.(`✗ ${task.id} · ${task.title} — ${errorMsg}`);
|
||||||
ctx.ui.notify(`Task ${task.id} failed: ${errorMsg}`, "error");
|
ctx.ui.notify(`Task ${task.id} failed: ${errorMsg}`, "error");
|
||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
@@ -658,9 +660,8 @@ async function executeTask(
|
|||||||
// release() would put the slot in freeSlots, then assign()
|
// release() would put the slot in freeSlots, then assign()
|
||||||
// would pick it right back up, getting stuck on the same model.
|
// would pick it right back up, getting stuck on the same model.
|
||||||
modelAttempt++;
|
modelAttempt++;
|
||||||
ctx.ui.notify(
|
sendChatMessage?.(
|
||||||
`Task ${task.id}: model failed, trying next (${modelAttempt + 1}/${maxModelAttempts}): ${result.error}`,
|
`~ ${task.id} · ${task.title} — trying model ${modelAttempt + 1}/${maxModelAttempts} (previous: ${result.error})`,
|
||||||
"warning",
|
|
||||||
);
|
);
|
||||||
break; // exit retry loop, cycle to next model
|
break; // exit retry loop, cycle to next model
|
||||||
}
|
}
|
||||||
@@ -668,9 +669,8 @@ async function executeTask(
|
|||||||
// No more models — use normal retry logic
|
// No more models — use normal retry logic
|
||||||
if (retries < maxRetries) {
|
if (retries < maxRetries) {
|
||||||
retries = progress.incrementRetry(task.id);
|
retries = progress.incrementRetry(task.id);
|
||||||
ctx.ui.notify(
|
sendChatMessage?.(
|
||||||
`Retrying task ${task.id} (${retries}/${maxRetries}): ${result.error}`,
|
`~ ${task.id} · ${task.title} — retrying (${retries}/${maxRetries}): ${result.error}`,
|
||||||
"warning",
|
|
||||||
);
|
);
|
||||||
|
|
||||||
// Exponential backoff
|
// Exponential backoff
|
||||||
@@ -679,6 +679,7 @@ async function executeTask(
|
|||||||
} else {
|
} else {
|
||||||
// Max retries exceeded
|
// Max retries exceeded
|
||||||
progress.markFailed(task.id, result.error || "Unknown error");
|
progress.markFailed(task.id, result.error || "Unknown error");
|
||||||
|
sendChatMessage?.(`✗ ${task.id} · ${task.title} — ${result.error}`);
|
||||||
ctx.ui.notify(
|
ctx.ui.notify(
|
||||||
`Task ${task.id} failed after ${maxRetries} retries: ${
|
`Task ${task.id} failed after ${maxRetries} retries: ${
|
||||||
result.error || "Unknown error"
|
result.error || "Unknown error"
|
||||||
@@ -691,6 +692,7 @@ async function executeTask(
|
|||||||
roundRobin?.release(task.id);
|
roundRobin?.release(task.id);
|
||||||
const errorMsg = error instanceof Error ? error.message : String(error);
|
const errorMsg = error instanceof Error ? error.message : String(error);
|
||||||
progress.markFailed(task.id, errorMsg);
|
progress.markFailed(task.id, errorMsg);
|
||||||
|
sendChatMessage?.(`✗ ${task.id} · ${task.title} — ${errorMsg}`);
|
||||||
ctx.ui.notify(`Task ${task.id} failed: ${errorMsg}`, "error");
|
ctx.ui.notify(`Task ${task.id} failed: ${errorMsg}`, "error");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -703,6 +705,9 @@ async function executeTask(
|
|||||||
// All models exhausted — release the slot
|
// All models exhausted — release the slot
|
||||||
roundRobin?.release(task.id);
|
roundRobin?.release(task.id);
|
||||||
progress.markFailed(task.id, "All configured models exhausted");
|
progress.markFailed(task.id, "All configured models exhausted");
|
||||||
|
sendChatMessage?.(
|
||||||
|
`✗ ${task.id} · ${task.title} — all ${maxModelAttempts} models exhausted`,
|
||||||
|
);
|
||||||
ctx.ui.notify(
|
ctx.ui.notify(
|
||||||
`Task ${task.id} failed: all configured models exhausted`,
|
`Task ${task.id} failed: all configured models exhausted`,
|
||||||
"error",
|
"error",
|
||||||
|
|||||||
Reference in New Issue
Block a user