opencode-ultra 0.7.1 → 0.7.2

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/config.d.ts CHANGED
@@ -73,11 +73,6 @@ declare const PluginConfigSchema: z.ZodObject<{
73
73
  maxTotalSpawned: z.ZodOptional<z.ZodNumber>;
74
74
  agentTimeoutMs: z.ZodOptional<z.ZodNumber>;
75
75
  }, z.core.$strip>>;
76
- evolve_auto: z.ZodOptional<z.ZodObject<{
77
- minScore: z.ZodOptional<z.ZodNumber>;
78
- maxProposals: z.ZodOptional<z.ZodNumber>;
79
- skipReview: z.ZodOptional<z.ZodBoolean>;
80
- }, z.core.$strip>>;
81
76
  }, z.core.$loose>;
82
77
  export type PluginConfig = z.infer<typeof PluginConfigSchema>;
83
78
  export declare function parsePluginConfig(raw: unknown): PluginConfig;
package/dist/index.js CHANGED
@@ -9,7 +9,6 @@ var __export = (target, all) => {
9
9
  set: (newValue) => all[name] = () => newValue
10
10
  });
11
11
  };
12
- var __require = import.meta.require;
13
12
 
14
13
  // node_modules/zod/v4/classic/external.js
15
14
  var exports_external = {};
@@ -14473,11 +14472,6 @@ var PluginConfigSchema = exports_external.object({
14473
14472
  safety: exports_external.object({
14474
14473
  maxTotalSpawned: exports_external.number().min(1).optional(),
14475
14474
  agentTimeoutMs: exports_external.number().min(1000).optional()
14476
- }).optional(),
14477
- evolve_auto: exports_external.object({
14478
- minScore: exports_external.number().min(0).max(30).optional(),
14479
- maxProposals: exports_external.number().min(1).max(10).optional(),
14480
- skipReview: exports_external.boolean().optional()
14481
14475
  }).optional()
14482
14476
  }).passthrough();
14483
14477
  function parsePluginConfig(raw) {
@@ -14536,8 +14530,7 @@ function mergeConfigs(base, override) {
14536
14530
  ])],
14537
14531
  background_task: override.background_task ?? base.background_task,
14538
14532
  comment_checker: override.comment_checker ?? base.comment_checker,
14539
- todo_enforcer: override.todo_enforcer ?? base.todo_enforcer,
14540
- evolve_auto: override.evolve_auto ?? base.evolve_auto
14533
+ todo_enforcer: override.todo_enforcer ?? base.todo_enforcer
14541
14534
  };
14542
14535
  }
14543
14536
  function detectConfigFile(basePath) {
@@ -14987,15 +14980,6 @@ ledger_save({
14987
14980
  })
14988
14981
  \`\`\`
14989
14982
 
14990
- ## AUTOMATED MODE
14991
-
14992
- The \`evolve_auto\` tool is available for autonomous execution:
14993
- - \`evolve_auto({dryRun: true})\` \u2014 Scan + filter only (preview proposals with scores)
14994
- - \`evolve_auto({})\` \u2014 Full autonomous cycle (scan \u2192 filter \u2192 implement \u2192 review \u2192 save)
14995
-
14996
- Use \`evolve_auto\` when the user requests autonomous self-improvement.
14997
- Manual exploration (the phases above) is still the default for human-guided evolve.
14998
-
14999
14983
  ## RULES
15000
14984
  - Use the capability inventory above as ground truth. No file reading needed.
15001
14985
  - Proposals must cite specific tools/hooks from the inventory for "Current state".
@@ -28616,388 +28600,6 @@ function formatResult(result) {
28616
28600
  `);
28617
28601
  }
28618
28602
 
28619
- // src/tools/evolve-filter.ts
28620
- var PRIORITY_WEIGHT = {
28621
- P0: 10,
28622
- P1: 5,
28623
- P2: 1
28624
- };
28625
- var EFFORT_WEIGHT = {
28626
- Low: 3,
28627
- Medium: 2,
28628
- High: 1
28629
- };
28630
- var DEFAULT_MIN_SCORE = 5;
28631
- var DEFAULT_MAX_PROPOSALS = 3;
28632
- function scoreProposal(p) {
28633
- const pw = PRIORITY_WEIGHT[p.priority] ?? 1;
28634
- const ew = EFFORT_WEIGHT[p.effort] ?? 1;
28635
- return pw * ew;
28636
- }
28637
- function filterProposals(proposals, config3) {
28638
- const minScore = config3?.minScore ?? DEFAULT_MIN_SCORE;
28639
- const maxProposals = config3?.maxProposals ?? DEFAULT_MAX_PROPOSALS;
28640
- const scored = proposals.map((p) => {
28641
- const score = scoreProposal(p);
28642
- const accepted2 = score >= minScore;
28643
- return {
28644
- ...p,
28645
- score,
28646
- accepted: accepted2,
28647
- reason: accepted2 ? undefined : `Score ${score} below threshold ${minScore}`
28648
- };
28649
- });
28650
- const accepted = scored.filter((p) => p.accepted).sort((a, b) => b.score - a.score).slice(0, maxProposals);
28651
- const rejected = scored.filter((p) => !p.accepted);
28652
- const acceptedSet = new Set(accepted);
28653
- const overflow = scored.filter((p) => p.accepted && !acceptedSet.has(p)).map((p) => ({ ...p, accepted: false, reason: `Exceeded maxProposals (${maxProposals})` }));
28654
- return [...accepted, ...overflow, ...rejected];
28655
- }
28656
- function parseProposalsFromMarkdown(markdown) {
28657
- const proposals = [];
28658
- const sections = markdown.split(/^##\s+Improvement:\s*/m);
28659
- for (let i = 1;i < sections.length; i++) {
28660
- const section = sections[i];
28661
- const lines = section.trim();
28662
- const titleMatch = lines.match(/^(.+?)(?:\n|$)/);
28663
- const title = titleMatch?.[1]?.trim() ?? "Untitled";
28664
- const priority = extractField(lines, "Priority");
28665
- const effort = extractField(lines, "Effort");
28666
- const description = extractField(lines, "Why") || extractField(lines, "How") || "";
28667
- const currentState = extractField(lines, "Current state") || undefined;
28668
- const inspiration = extractField(lines, "Inspiration") || undefined;
28669
- const normalizedPriority = normalizePriority(priority);
28670
- const normalizedEffort = normalizeEffort(effort);
28671
- if (normalizedPriority && normalizedEffort) {
28672
- proposals.push({
28673
- title,
28674
- priority: normalizedPriority,
28675
- effort: normalizedEffort,
28676
- description,
28677
- currentState,
28678
- inspiration
28679
- });
28680
- }
28681
- }
28682
- return proposals;
28683
- }
28684
- function extractField(text, fieldName) {
28685
- const pattern = new RegExp(`\\*\\*${fieldName}\\*\\*\\s*:\\s*(.+?)(?:\\n|$)`, "i");
28686
- const match = text.match(pattern);
28687
- return match?.[1]?.trim() ?? "";
28688
- }
28689
- function normalizePriority(raw) {
28690
- const upper = raw.toUpperCase().trim();
28691
- if (upper.startsWith("P0"))
28692
- return "P0";
28693
- if (upper.startsWith("P1"))
28694
- return "P1";
28695
- if (upper.startsWith("P2"))
28696
- return "P2";
28697
- return null;
28698
- }
28699
- function normalizeEffort(raw) {
28700
- const lower = raw.toLowerCase().trim();
28701
- if (lower.startsWith("low"))
28702
- return "Low";
28703
- if (lower.startsWith("medium") || lower.startsWith("med"))
28704
- return "Medium";
28705
- if (lower.startsWith("high"))
28706
- return "High";
28707
- return null;
28708
- }
28709
-
28710
- // src/tools/evolve-auto.ts
28711
- var DEFAULT_AGENT_TIMEOUT_MS2 = 300000;
28712
- var DEFAULT_MAX_SPAWNED = 15;
28713
- async function withTimeout3(promise3, ms, label) {
28714
- let timer;
28715
- const timeout = new Promise((_, reject) => {
28716
- timer = setTimeout(() => reject(new Error(`Timeout: ${label} exceeded ${ms}ms`)), ms);
28717
- });
28718
- try {
28719
- return await Promise.race([promise3, timeout]);
28720
- } finally {
28721
- clearTimeout(timer);
28722
- }
28723
- }
28724
- async function runEphemeralAgent(ctx, agentName, prompt, internalSessions, timeoutMs) {
28725
- const sessionResp = await ctx.client.session.create({
28726
- body: {},
28727
- query: { directory: ctx.directory }
28728
- });
28729
- const sessionID = sessionResp.data?.id;
28730
- if (!sessionID)
28731
- throw new Error("Failed to create session");
28732
- internalSessions.add(sessionID);
28733
- try {
28734
- await withTimeout3(ctx.client.session.prompt({
28735
- path: { id: sessionID },
28736
- body: {
28737
- parts: [{ type: "text", text: prompt }],
28738
- agent: agentName
28739
- },
28740
- query: { directory: ctx.directory }
28741
- }), timeoutMs, `${agentName}`);
28742
- const messagesResp = await ctx.client.session.messages({
28743
- path: { id: sessionID },
28744
- query: { directory: ctx.directory }
28745
- });
28746
- const messages = messagesResp.data ?? [];
28747
- const lastAssistant = messages.filter((m) => m.info?.role === "assistant").pop();
28748
- const rawResult = lastAssistant?.parts?.filter((p) => p.type === "text" && p.text).map((p) => p.text).join(`
28749
- `) ?? "(No response)";
28750
- return sanitizeSpawnResult(rawResult);
28751
- } finally {
28752
- internalSessions.delete(sessionID);
28753
- await ctx.client.session.delete({ path: { id: sessionID }, query: { directory: ctx.directory } }).catch(() => {});
28754
- }
28755
- }
28756
- function buildEvolvePrompt(evolveCtx) {
28757
- const inventory = evolveCtx ? buildInventorySection(evolveCtx) : "(No capability data)";
28758
- return `You are performing an automated self-improvement scan for the opencode-ultra plugin.
28759
-
28760
- ## Current Capabilities
28761
- ${inventory}
28762
-
28763
- ## Task
28764
- 1. Research the OpenCode plugin ecosystem (npm, GitHub) for features that opencode-ultra lacks
28765
- 2. Compare capabilities and identify gaps
28766
- 3. Propose concrete improvements in this EXACT format for EACH proposal:
28767
-
28768
- ## Improvement: [Feature Name]
28769
- **Inspiration**: [Plugin name] \u2014 [what it does]
28770
- **Current state**: [what opencode-ultra has now]
28771
- **Why**: [concrete benefit]
28772
- **How**: [which file to modify, what to add]
28773
- **Effort**: Low / Medium / High
28774
- **Priority**: P0 / P1 / P2
28775
-
28776
- Sort by Priority then Effort. Be specific and actionable.`;
28777
- }
28778
- function buildInventorySection(ctx) {
28779
- const lines = [];
28780
- lines.push("### Tools: " + ctx.tools.join(", "));
28781
- lines.push("### Hooks: " + ctx.hooks.join(", "));
28782
- lines.push("### Agents");
28783
- for (const [name, def] of Object.entries(ctx.agents)) {
28784
- lines.push(`- ${name} (${def.model}) \u2014 ${def.description}`);
28785
- }
28786
- lines.push("### Features: " + ctx.features.join(", "));
28787
- return lines.join(`
28788
- `);
28789
- }
28790
- function buildImplementPrompt(proposal) {
28791
- return `Implement the following improvement for opencode-ultra:
28792
-
28793
- ## ${proposal.title}
28794
- - **Priority**: ${proposal.priority}
28795
- - **Effort**: ${proposal.effort}
28796
- - **Description**: ${proposal.description}
28797
- ${proposal.currentState ? `- **Current state**: ${proposal.currentState}` : ""}
28798
- ${proposal.inspiration ? `- **Inspiration**: ${proposal.inspiration}` : ""}
28799
- ${proposal.files ? `- **Files to modify**: ${proposal.files.join(", ")}` : ""}
28800
-
28801
- Implement this change. Be precise and minimal. Only modify what is necessary.`;
28802
- }
28803
- function buildReviewPrompt(implementations) {
28804
- return `Review the following implementation results from an automated evolve cycle:
28805
-
28806
- ${implementations.map((impl, i) => `### Implementation ${i + 1}
28807
- ${impl}`).join(`
28808
-
28809
- ---
28810
-
28811
- `)}
28812
-
28813
- Check for:
28814
- 1. Breaking changes
28815
- 2. Missing imports or type errors
28816
- 3. Logic bugs
28817
- 4. Security concerns
28818
- 5. Inconsistencies with existing code patterns
28819
-
28820
- Report any blocking issues. If everything looks good, confirm with "LGTM".`;
28821
- }
28822
- function formatFinalReport(result) {
28823
- const lines = [];
28824
- lines.push("# Evolve Auto Results");
28825
- lines.push("");
28826
- if (result.proposals) {
28827
- const accepted = result.proposals.filter((p) => p.accepted);
28828
- const rejected = result.proposals.filter((p) => !p.accepted);
28829
- lines.push("## Proposals");
28830
- lines.push("");
28831
- if (accepted.length > 0) {
28832
- lines.push("### Accepted");
28833
- for (const p of accepted) {
28834
- lines.push(`- **${p.title}** [${p.priority}/${p.effort}] score=${p.score}`);
28835
- }
28836
- lines.push("");
28837
- }
28838
- if (rejected.length > 0) {
28839
- lines.push("### Rejected");
28840
- for (const p of rejected) {
28841
- lines.push(`- **${p.title}** [${p.priority}/${p.effort}] score=${p.score} \u2014 ${p.reason}`);
28842
- }
28843
- lines.push("");
28844
- }
28845
- }
28846
- if (result.implementations && result.implementations.length > 0) {
28847
- lines.push("## Implementations");
28848
- lines.push("");
28849
- for (let i = 0;i < result.implementations.length; i++) {
28850
- lines.push(`### Implementation ${i + 1}`);
28851
- lines.push(result.implementations[i]);
28852
- lines.push("");
28853
- }
28854
- }
28855
- if (result.reviewOutput) {
28856
- lines.push("## Review");
28857
- lines.push(result.reviewOutput);
28858
- lines.push("");
28859
- }
28860
- if (result.savedKey) {
28861
- lines.push(`## Saved as \`${result.savedKey}\``);
28862
- }
28863
- return lines.join(`
28864
- `);
28865
- }
28866
- function createEvolveAutoTool(ctx, internalSessions, deps) {
28867
- const timeoutMs = deps.agentTimeoutMs ?? DEFAULT_AGENT_TIMEOUT_MS2;
28868
- return tool({
28869
- description: `Autonomous self-improvement cycle for opencode-ultra.
28870
-
28871
- Runs a 5-phase loop: SCAN \u2192 FILTER \u2192 IMPLEMENT \u2192 REVIEW \u2192 SAVE
28872
-
28873
- Phases:
28874
- 1. SCAN \u2014 Sisyphus scans ecosystem and proposes improvements
28875
- 2. FILTER \u2014 Score and filter proposals by priority \xD7 effort
28876
- 3. IMPLEMENT \u2014 Hephaestus implements each accepted proposal sequentially
28877
- 4. REVIEW \u2014 Momus reviews all implementations
28878
- 5. SAVE \u2014 Results saved to continuity ledger
28879
-
28880
- Use dryRun: true to run SCAN + FILTER only (preview proposals without implementing).`,
28881
- args: {
28882
- dryRun: tool.schema.boolean().optional().describe("SCAN+FILTER only, no implementation (default: false)"),
28883
- maxProposals: tool.schema.number().optional().describe("Max proposals to implement per cycle (default: 3)"),
28884
- minScore: tool.schema.number().optional().describe("Minimum score threshold (default: 5)"),
28885
- skipReview: tool.schema.boolean().optional().describe("Skip momus review phase (default: false)")
28886
- },
28887
- execute: async (args, toolCtx) => {
28888
- const dryRun = args.dryRun ?? false;
28889
- const minScore = args.minScore ?? deps.evolveAutoConfig?.minScore ?? 5;
28890
- const maxProposals = args.maxProposals ?? deps.evolveAutoConfig?.maxProposals ?? 3;
28891
- const skipReview = args.skipReview ?? deps.evolveAutoConfig?.skipReview ?? false;
28892
- const result = { phase: "INIT" };
28893
- if (internalSessions.size >= DEFAULT_MAX_SPAWNED - 1) {
28894
- return "Error: Too many concurrent sessions. Wait for active sessions to complete.";
28895
- }
28896
- result.phase = "SCAN";
28897
- toolCtx.metadata({ title: "evolve_auto: SCAN \u2014 scanning ecosystem..." });
28898
- log("evolve_auto: Phase 1 SCAN starting");
28899
- let scanOutput;
28900
- try {
28901
- scanOutput = await runEphemeralAgent(ctx, "sisyphus", buildEvolvePrompt(deps.evolveCtx), internalSessions, timeoutMs);
28902
- result.scanOutput = scanOutput;
28903
- } catch (err) {
28904
- const msg = err instanceof Error ? err.message : String(err);
28905
- log("evolve_auto: SCAN failed", { error: msg });
28906
- return `## evolve_auto: SCAN Failed
28907
-
28908
- **Error**: ${msg}`;
28909
- }
28910
- result.phase = "FILTER";
28911
- toolCtx.metadata({ title: "evolve_auto: FILTER \u2014 scoring proposals..." });
28912
- log("evolve_auto: Phase 2 PARSE+FILTER");
28913
- const parsed = parseProposalsFromMarkdown(scanOutput);
28914
- if (parsed.length === 0) {
28915
- log("evolve_auto: no proposals parsed from scan output");
28916
- return `## evolve_auto: No Proposals
28917
-
28918
- Sisyphus scan completed but no improvement proposals were found in the output.
28919
-
28920
- ### Raw Scan Output
28921
- ${scanOutput}`;
28922
- }
28923
- const filtered = filterProposals(parsed, { minScore, maxProposals });
28924
- result.proposals = filtered;
28925
- const accepted = filtered.filter((p) => p.accepted);
28926
- log("evolve_auto: filtered", {
28927
- total: parsed.length,
28928
- accepted: accepted.length,
28929
- rejected: filtered.length - accepted.length
28930
- });
28931
- if (accepted.length === 0) {
28932
- return `## evolve_auto: All Proposals Rejected
28933
-
28934
- Parsed ${parsed.length} proposals, but none met the threshold (minScore=${minScore}).
28935
-
28936
- ${formatFinalReport(result)}`;
28937
- }
28938
- if (dryRun) {
28939
- toolCtx.metadata({ title: `evolve_auto: DRY RUN \u2014 ${accepted.length} proposals accepted` });
28940
- return `## evolve_auto: Dry Run Complete
28941
-
28942
- ${formatFinalReport(result)}
28943
-
28944
- > Use \`evolve_auto({})\` for full execution.`;
28945
- }
28946
- result.phase = "IMPLEMENT";
28947
- result.implementations = [];
28948
- log("evolve_auto: Phase 3 IMPLEMENT", { count: accepted.length });
28949
- for (let i = 0;i < accepted.length; i++) {
28950
- const proposal = accepted[i];
28951
- toolCtx.metadata({
28952
- title: `evolve_auto: IMPLEMENT [${i + 1}/${accepted.length}] \u2014 ${proposal.title}`
28953
- });
28954
- try {
28955
- const implResult = await runEphemeralAgent(ctx, "hephaestus", buildImplementPrompt(proposal), internalSessions, timeoutMs);
28956
- result.implementations.push(implResult);
28957
- log(`evolve_auto: implemented ${i + 1}/${accepted.length}`, { title: proposal.title });
28958
- } catch (err) {
28959
- const msg = err instanceof Error ? err.message : String(err);
28960
- log(`evolve_auto: IMPLEMENT failed for "${proposal.title}"`, { error: msg });
28961
- result.implementations.push(`**FAILED**: ${proposal.title} \u2014 ${msg}`);
28962
- break;
28963
- }
28964
- }
28965
- if (!skipReview && result.implementations.length > 0) {
28966
- result.phase = "REVIEW";
28967
- toolCtx.metadata({ title: "evolve_auto: REVIEW \u2014 momus reviewing..." });
28968
- log("evolve_auto: Phase 4 REVIEW");
28969
- try {
28970
- const reviewResult = await runEphemeralAgent(ctx, "momus", buildReviewPrompt(result.implementations), internalSessions, timeoutMs);
28971
- result.reviewOutput = reviewResult;
28972
- } catch (err) {
28973
- const msg = err instanceof Error ? err.message : String(err);
28974
- log("evolve_auto: REVIEW failed", { error: msg });
28975
- result.reviewOutput = `Review failed: ${msg}`;
28976
- }
28977
- }
28978
- result.phase = "SAVE";
28979
- toolCtx.metadata({ title: "evolve_auto: SAVE \u2014 saving results..." });
28980
- log("evolve_auto: Phase 5 SAVE");
28981
- const dateStr = new Date().toISOString().slice(0, 10);
28982
- const savedKey = `evolve-auto-${dateStr}`;
28983
- result.savedKey = savedKey;
28984
- const report = formatFinalReport(result);
28985
- const fs8 = await import("fs");
28986
- const path8 = await import("path");
28987
- const ledgerDir2 = path8.join(ctx.directory, ".opencode", "ledgers");
28988
- try {
28989
- await fs8.promises.mkdir(ledgerDir2, { recursive: true });
28990
- await fs8.promises.writeFile(path8.join(ledgerDir2, `${savedKey}.md`), report, "utf-8");
28991
- log("evolve_auto: saved to ledger", { key: savedKey });
28992
- } catch (err) {
28993
- log("evolve_auto: failed to save ledger", { error: String(err) });
28994
- }
28995
- toolCtx.metadata({ title: `evolve_auto: DONE \u2014 ${accepted.length} improvements` });
28996
- return report;
28997
- }
28998
- });
28999
- }
29000
-
29001
28603
  // src/hooks/todo-enforcer.ts
29002
28604
  var DEFAULT_MAX_ENFORCEMENTS = 5;
29003
28605
  var sessionState = new Map;
@@ -29533,14 +29135,6 @@ var OpenCodeUltra = async (ctx) => {
29533
29135
  if (!disabledTools.has("evolve_apply")) {
29534
29136
  toolRegistry.evolve_apply = evolveApply;
29535
29137
  }
29536
- if (!disabledTools.has("evolve_auto")) {
29537
- const evolveAuto = createEvolveAutoTool(ctx, internalSessions, {
29538
- agentTimeoutMs: safetyConfig.agentTimeoutMs,
29539
- evolveCtx: buildEvolveCtx(Object.keys(toolRegistry)),
29540
- evolveAutoConfig: pluginConfig.evolve_auto
29541
- });
29542
- toolRegistry.evolve_auto = evolveAuto;
29543
- }
29544
29138
  return {
29545
29139
  tool: toolRegistry,
29546
29140
  config: async (config3) => {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "opencode-ultra",
3
- "version": "0.7.1",
3
+ "version": "0.7.2",
4
4
  "description": "Lightweight OpenCode 1.2.x plugin — ultrawork mode, multi-agent orchestration, rules injection",
5
5
  "keywords": [
6
6
  "opencode",