opencode-usage-coach 0.8.0 → 0.8.1
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 +62 -33
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -1682,7 +1682,7 @@ ${priorNotes}
|
|
|
1682
1682
|
}
|
|
1683
1683
|
}),
|
|
1684
1684
|
generate_batch: tool({
|
|
1685
|
-
description: "Run the GENERATOR model on MULTIPLE tasks. Quota-aware: GO = full parallel; THROTTLE = lighter model + concurrency capped at 2; STOP = refused. Use for INDEPENDENT tasks. Step-limited: each sub-session aborts after max_steps (default 30).",
|
|
1685
|
+
description: "Run the GENERATOR model on MULTIPLE tasks. Quota-aware: GO = full parallel; THROTTLE = lighter model + concurrency capped at 2; STOP = refused. Use for INDEPENDENT tasks. Step-limited: each sub-session aborts after max_steps (default 30). Resilient: failed tasks are retried sequentially (once); if retry also fails, re-run them individually with generate().",
|
|
1686
1686
|
args: { tasks: tool.schema.array(tool.schema.object({ id: tool.schema.number(), prompt: tool.schema.string() })), max_steps: tool.schema.number().optional().describe("Maximum sub-session steps per task before timeout (default 30).") },
|
|
1687
1687
|
async execute(args, ctx) {
|
|
1688
1688
|
const cfg = readHarnessCfg(ctx.directory);
|
|
@@ -1698,54 +1698,83 @@ ${priorNotes}
|
|
|
1698
1698
|
const limit = decision === "THROTTLE" ? 2 : args.tasks.length;
|
|
1699
1699
|
const rules = readRules();
|
|
1700
1700
|
const priorNotes = readImplNotes(5);
|
|
1701
|
-
const
|
|
1702
|
-
|
|
1703
|
-
|
|
1704
|
-
const out = await Promise.all(batch.map(async (t) => {
|
|
1705
|
-
let prefix = rules ? `Lessons learned from previous failures (apply where relevant):
|
|
1701
|
+
const maxSteps = args.max_steps ?? DEFAULT_MAX_STEPS;
|
|
1702
|
+
const runOne = async (t) => {
|
|
1703
|
+
let prefix = rules ? `Lessons learned from previous failures (apply where relevant):
|
|
1706
1704
|
${rules}
|
|
1707
1705
|
|
|
1708
1706
|
---
|
|
1709
1707
|
|
|
1710
1708
|
` : "";
|
|
1711
|
-
|
|
1709
|
+
if (priorNotes) prefix = `Notes from previous runs (context for this task):
|
|
1712
1710
|
${priorNotes}
|
|
1713
1711
|
|
|
1714
1712
|
---
|
|
1715
1713
|
|
|
1716
1714
|
` + prefix;
|
|
1717
|
-
|
|
1718
|
-
|
|
1719
|
-
|
|
1720
|
-
|
|
1721
|
-
|
|
1722
|
-
|
|
1723
|
-
|
|
1724
|
-
|
|
1725
|
-
|
|
1726
|
-
|
|
1727
|
-
|
|
1728
|
-
|
|
1729
|
-
|
|
1730
|
-
|
|
1731
|
-
|
|
1732
|
-
|
|
1733
|
-
|
|
1734
|
-
|
|
1735
|
-
|
|
1736
|
-
}
|
|
1715
|
+
prefix += IMPL_NOTE_INSTRUCTION;
|
|
1716
|
+
const r = await runModel(
|
|
1717
|
+
input.client,
|
|
1718
|
+
model,
|
|
1719
|
+
prefix + t.prompt,
|
|
1720
|
+
ctx.directory,
|
|
1721
|
+
{ sessionID: ctx.sessionID, taskId: t.id },
|
|
1722
|
+
maxSteps
|
|
1723
|
+
);
|
|
1724
|
+
const isTimeoutOrError = r.startsWith("Task appears too large") || r.startsWith("ERROR:");
|
|
1725
|
+
if (!isTimeoutOrError) {
|
|
1726
|
+
try {
|
|
1727
|
+
const { notes } = extractImplNotes(r);
|
|
1728
|
+
if (notes) {
|
|
1729
|
+
appendImplNotes(notes, t.prompt);
|
|
1730
|
+
const kw = extractKeywords(t.prompt);
|
|
1731
|
+
if (kw.length) {
|
|
1732
|
+
const noteNodeId = saveInvestigationResult(kw, notes, "impl-note", 0.5);
|
|
1733
|
+
if (noteNodeId) linkImplNoteToDomain(noteNodeId, notes);
|
|
1737
1734
|
}
|
|
1738
|
-
} catch (e) {
|
|
1739
|
-
log(`generate_batch impl-notes extract err: ${String(e)}`);
|
|
1740
1735
|
}
|
|
1736
|
+
} catch (e) {
|
|
1737
|
+
log(`generate_batch impl-notes extract err: ${String(e)}`);
|
|
1738
|
+
}
|
|
1739
|
+
}
|
|
1740
|
+
return { id: t.id, result: r };
|
|
1741
|
+
};
|
|
1742
|
+
const results = [];
|
|
1743
|
+
const failed = [];
|
|
1744
|
+
for (let i = 0; i < args.tasks.length; i += limit) {
|
|
1745
|
+
const batch = args.tasks.slice(i, i + limit);
|
|
1746
|
+
const settled = await Promise.allSettled(batch.map((t) => runOne(t)));
|
|
1747
|
+
for (let j = 0; j < settled.length; j++) {
|
|
1748
|
+
const s = settled[j];
|
|
1749
|
+
const t = batch[j];
|
|
1750
|
+
if (s.status === "fulfilled") {
|
|
1751
|
+
results.push(`[task ${s.value.id}] ${s.value.result}`);
|
|
1752
|
+
} else {
|
|
1753
|
+
const err = String(s.reason ?? "unknown rejection");
|
|
1754
|
+
log(`generate_batch task ${t.id} REJECTED: ${err}`);
|
|
1755
|
+
failed.push({ id: t.id, prompt: t.prompt, error: err });
|
|
1741
1756
|
}
|
|
1742
|
-
|
|
1743
|
-
|
|
1744
|
-
|
|
1757
|
+
}
|
|
1758
|
+
}
|
|
1759
|
+
if (failed.length > 0) {
|
|
1760
|
+
log(`generate_batch: retrying ${failed.length} failed task(s) sequentially`);
|
|
1761
|
+
for (const f of failed) {
|
|
1762
|
+
try {
|
|
1763
|
+
const r = await runOne(f);
|
|
1764
|
+
results.push(`[task ${r.id}] (retry) ${r.result}`);
|
|
1765
|
+
} catch (e2) {
|
|
1766
|
+
const err2 = String(e2 ?? "unknown rejection on retry");
|
|
1767
|
+
log(`generate_batch task ${f.id} retry ALSO FAILED: ${err2}`);
|
|
1768
|
+
results.push(`[task ${f.id}] ERROR: task failed on both batch and retry. Error: ${err2}`);
|
|
1769
|
+
}
|
|
1770
|
+
}
|
|
1745
1771
|
}
|
|
1772
|
+
const failedCount = results.filter((r) => r.includes("ERROR:")).length;
|
|
1746
1773
|
const note = throttle ? `
|
|
1747
1774
|
[usage-coach] quota THROTTLE \u2014 lighter model ${cfg.lighterModel}, concurrency capped at ${limit}` : "";
|
|
1748
|
-
|
|
1775
|
+
const failNext = failedCount > 0 ? `
|
|
1776
|
+
[usage-coach NEXT] ${failedCount} task(s) failed even after retry. Re-run each failed task individually with generate() (sequential), NOT generate_batch. This isolates failures and avoids batch-level abort.` : "";
|
|
1777
|
+
return results.join("\n\n") + note + failNext;
|
|
1749
1778
|
}
|
|
1750
1779
|
}),
|
|
1751
1780
|
grade: tool({
|
package/package.json
CHANGED