opencode-ultra 0.4.1 → 0.5.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/hooks/keyword-detector.d.ts +1 -1
- package/dist/index.js +206 -20
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -14465,7 +14465,11 @@ var PluginConfigSchema = exports_external.object({
|
|
|
14465
14465
|
todo_enforcer: exports_external.object({
|
|
14466
14466
|
maxEnforcements: exports_external.number().min(0).optional()
|
|
14467
14467
|
}).optional(),
|
|
14468
|
-
mcp_api_keys: exports_external.record(exports_external.string(), exports_external.string()).optional()
|
|
14468
|
+
mcp_api_keys: exports_external.record(exports_external.string(), exports_external.string()).optional(),
|
|
14469
|
+
safety: exports_external.object({
|
|
14470
|
+
maxTotalSpawned: exports_external.number().min(1).optional(),
|
|
14471
|
+
agentTimeoutMs: exports_external.number().min(1000).optional()
|
|
14472
|
+
}).optional()
|
|
14469
14473
|
}).passthrough();
|
|
14470
14474
|
function parsePluginConfig(raw) {
|
|
14471
14475
|
const result = PluginConfigSchema.safeParse(raw);
|
|
@@ -14633,6 +14637,40 @@ var BUILTIN_AGENTS = {
|
|
|
14633
14637
|
mode: "subagent",
|
|
14634
14638
|
reasoningEffort: "medium",
|
|
14635
14639
|
maxTokens: 64000
|
|
14640
|
+
},
|
|
14641
|
+
scout: {
|
|
14642
|
+
model: "anthropic/claude-sonnet-4-5",
|
|
14643
|
+
description: "Plugin ecosystem researcher \u2014 finds, analyzes, and compares OpenCode plugins for self-improvement",
|
|
14644
|
+
prompt: `You are Scout, an OpenCode plugin ecosystem researcher.
|
|
14645
|
+
|
|
14646
|
+
## YOUR MISSION
|
|
14647
|
+
Search the web (npm, GitHub, OpenCode community) for OpenCode plugins and extensions.
|
|
14648
|
+
Analyze their features, architecture, and quality. Compare with opencode-ultra.
|
|
14649
|
+
|
|
14650
|
+
## SEARCH STRATEGY
|
|
14651
|
+
1. Search npm for "opencode-plugin", "opencode-ai", "@opencode" packages
|
|
14652
|
+
2. Search GitHub for "opencode plugin", "opencode extension", "oh-my-opencode"
|
|
14653
|
+
3. Look at package.json dependencies on @opencode-ai/plugin or @opencode-ai/sdk
|
|
14654
|
+
4. Read README files and source code of discovered plugins
|
|
14655
|
+
|
|
14656
|
+
## OUTPUT FORMAT
|
|
14657
|
+
For each plugin found, report:
|
|
14658
|
+
- **Name**: package name + repo URL
|
|
14659
|
+
- **Version**: latest version
|
|
14660
|
+
- **Features**: bullet list of capabilities
|
|
14661
|
+
- **Architecture**: hook types used, tool count, agent count
|
|
14662
|
+
- **Quality signals**: test count, TypeScript, last updated, download count
|
|
14663
|
+
- **Unique ideas**: features that opencode-ultra does NOT have
|
|
14664
|
+
|
|
14665
|
+
## COMPARISON
|
|
14666
|
+
After listing plugins, generate a structured gap analysis:
|
|
14667
|
+
- Features others have that opencode-ultra lacks
|
|
14668
|
+
- Features opencode-ultra has that others lack (competitive advantages)
|
|
14669
|
+
- Improvement priority list (high/medium/low impact)
|
|
14670
|
+
|
|
14671
|
+
Be thorough but focused. Skip abandoned or trivial plugins.`,
|
|
14672
|
+
mode: "subagent",
|
|
14673
|
+
maxTokens: 32000
|
|
14636
14674
|
}
|
|
14637
14675
|
};
|
|
14638
14676
|
function buildAgentTable(agents) {
|
|
@@ -14763,6 +14801,7 @@ var ULTRAWORK_PATTERN = /\b(ultrawork|ulw)\b/i;
|
|
|
14763
14801
|
var THINK_PATTERN = /\b(think\s+hard|think\s+through|think\s+deeply|think\s+carefully)\b|\u3058\u3063\u304F\u308A|\u6DF1\u304F\u8003\u3048\u3066|\u719F\u8003/i;
|
|
14764
14802
|
var SEARCH_PATTERN = /\b(search|find|locate|lookup|look\s*up|explore|discover|scan|grep|query|browse|detect|trace|seek|track|pinpoint|hunt)\b|where\s+is|show\s+me|list\s+all|\u691C\u7D22|\u63A2\u3057\u3066|\u898B\u3064\u3051\u3066|\u30B5\u30FC\u30C1|\u63A2\u7D22|\u30B9\u30AD\u30E3\u30F3|\u3069\u3053|\u767A\u898B|\u635C\u7D22|\u898B\u3064\u3051\u51FA\u3059|\u4E00\u89A7|\u641C\u7D22|\u67E5\u627E|\u5BFB\u627E|\u67E5\u8BE2|\u68C0\u7D22|\u5B9A\u4F4D|\u626B\u63CF|\u53D1\u73B0|\u5728\u54EA\u91CC|\u627E\u51FA\u6765|\u5217\u51FA/i;
|
|
14765
14803
|
var ANALYZE_PATTERN = /\b(analyze|analyse|investigate|examine|research|study|deep[\s-]?dive|inspect|audit|evaluate|assess|review|diagnose|scrutinize|dissect|debug|comprehend|interpret|breakdown|understand)\b|why\s+is|how\s+does|how\s+to|\u5206\u6790|\u8ABF\u67FB|\u89E3\u6790|\u691C\u8A0E|\u7814\u7A76|\u8A3A\u65AD|\u7406\u89E3|\u8AAC\u660E|\u691C\u8A3C|\u7CBE\u67FB|\u7A76\u660E|\u30C7\u30D0\u30C3\u30B0|\u306A\u305C|\u3069\u3046|\u4ED5\u7D44\u307F|\u8C03\u67E5|\u68C0\u67E5|\u5256\u6790|\u6DF1\u5165|\u8BCA\u65AD|\u89E3\u91CA|\u8C03\u8BD5|\u4E3A\u4EC0\u4E48|\u539F\u7406|\u641E\u6E05\u695A|\u5F04\u660E\u767D/i;
|
|
14804
|
+
var EVOLVE_PATTERN = /\b(evolve|self[\s-]?improve|self[\s-]?upgrade|plugin[\s-]?scout|ecosystem[\s-]?scan)\b|\u81EA\u5DF1\u6539\u5584|\u9032\u5316|\u30D7\u30E9\u30B0\u30A4\u30F3\u63A2\u7D22|\u30A8\u30B3\u30B7\u30B9\u30C6\u30E0|\u81EA\u6211\u8FDB\u5316|\u63D2\u4EF6\u641C\u7D22/i;
|
|
14766
14805
|
function removeCodeBlocks(text) {
|
|
14767
14806
|
return text.replace(CODE_BLOCK_PATTERN, "").replace(INLINE_CODE_PATTERN, "");
|
|
14768
14807
|
}
|
|
@@ -14773,7 +14812,8 @@ var KEYWORD_DEFS = [
|
|
|
14773
14812
|
{ pattern: ULTRAWORK_PATTERN, type: "ultrawork", getMessage: () => ULTRAWORK_MESSAGE },
|
|
14774
14813
|
{ pattern: SEARCH_PATTERN, type: "search", getMessage: () => SEARCH_MESSAGE },
|
|
14775
14814
|
{ pattern: ANALYZE_PATTERN, type: "analyze", getMessage: () => ANALYZE_MESSAGE },
|
|
14776
|
-
{ pattern: THINK_PATTERN, type: "think", getMessage: () => THINK_MESSAGE }
|
|
14815
|
+
{ pattern: THINK_PATTERN, type: "think", getMessage: () => THINK_MESSAGE },
|
|
14816
|
+
{ pattern: EVOLVE_PATTERN, type: "evolve", getMessage: () => EVOLVE_MESSAGE }
|
|
14777
14817
|
];
|
|
14778
14818
|
function detectKeywords(text) {
|
|
14779
14819
|
const clean = removeCodeBlocks(text);
|
|
@@ -14863,6 +14903,33 @@ IF COMPLEX \u2014 DO NOT STRUGGLE ALONE. Consult specialists:
|
|
|
14863
14903
|
|
|
14864
14904
|
SYNTHESIZE findings before proceeding.`;
|
|
14865
14905
|
var THINK_MESSAGE = `Extended thinking enabled. Take your time to reason thoroughly.`;
|
|
14906
|
+
var EVOLVE_MESSAGE = `[evolve-mode] SELF-IMPROVEMENT CYCLE ACTIVATED.
|
|
14907
|
+
|
|
14908
|
+
## MISSION
|
|
14909
|
+
Search the OpenCode plugin ecosystem, find what others have built, and identify gaps in opencode-ultra.
|
|
14910
|
+
|
|
14911
|
+
## STEPS
|
|
14912
|
+
1. **SCOUT** \u2014 Spawn the **scout** agent to search npm/GitHub for OpenCode plugins
|
|
14913
|
+
2. **READ SELF** \u2014 Read opencode-ultra's own README.md and key source files to know current capabilities
|
|
14914
|
+
3. **COMPARE** \u2014 Generate a structured gap analysis (what we have vs what we're missing)
|
|
14915
|
+
4. **PRIORITIZE** \u2014 Rank missing features by impact (high/medium/low)
|
|
14916
|
+
5. **PROPOSE** \u2014 Present improvement plan to the user
|
|
14917
|
+
6. **SAVE** \u2014 Save findings to a continuity ledger via ledger_save for future reference
|
|
14918
|
+
|
|
14919
|
+
## EXECUTION
|
|
14920
|
+
\`\`\`
|
|
14921
|
+
spawn_agent({
|
|
14922
|
+
agents: [
|
|
14923
|
+
{agent: "scout", prompt: "Search npm and GitHub for OpenCode 1.2.x plugins. Find all published plugins, analyze features, compare with opencode-ultra.", description: "Plugin ecosystem scan"},
|
|
14924
|
+
{agent: "explore", prompt: "Read opencode-ultra's README.md, src/index.ts, src/agents/index.ts to catalog current features", description: "Self-analysis"}
|
|
14925
|
+
]
|
|
14926
|
+
})
|
|
14927
|
+
\`\`\`
|
|
14928
|
+
|
|
14929
|
+
After gathering results, synthesize into a gap analysis and present to the user.
|
|
14930
|
+
Save the analysis via: ledger_save({name: "evolve-scan-YYYY-MM-DD", content: "..."})
|
|
14931
|
+
|
|
14932
|
+
**This is how opencode-ultra gets better \u2014 by knowing what it doesn't know.**`;
|
|
14866
14933
|
|
|
14867
14934
|
// src/hooks/rules-injector.ts
|
|
14868
14935
|
import * as fs2 from "fs";
|
|
@@ -27363,16 +27430,89 @@ function resolveCategory(categoryName, configCategories) {
|
|
|
27363
27430
|
return { ...builtin, ...override };
|
|
27364
27431
|
}
|
|
27365
27432
|
|
|
27433
|
+
// src/safety/sanitizer.ts
|
|
27434
|
+
var INJECTION_PATTERNS = [
|
|
27435
|
+
{ pattern: /ignore\s+(?:all\s+)?(?:previous\s+|prior\s+|above\s+)?instructions/i, label: "instruction-override" },
|
|
27436
|
+
{ pattern: /disregard\s+(?:all\s+)?(?:previous\s+|prior\s+)?(?:instructions|rules|guidelines)/i, label: "instruction-override" },
|
|
27437
|
+
{ pattern: /forget\s+(?:all\s+)?(?:previous\s+|prior\s+)?(?:instructions|context|rules)/i, label: "instruction-override" },
|
|
27438
|
+
{ pattern: /override\s+(?:all\s+)?(?:previous\s+|prior\s+)?(?:instructions|rules|system)/i, label: "instruction-override" },
|
|
27439
|
+
{ pattern: /you\s+are\s+now\s+(?:a\s+|an\s+|in\s+)?/i, label: "role-hijack" },
|
|
27440
|
+
{ pattern: /pretend\s+(?:you\s+are|to\s+be|you're)\s+/i, label: "role-hijack" },
|
|
27441
|
+
{ pattern: /act\s+as\s+(?:if\s+)?(?:you\s+(?:are|were)\s+)?/i, label: "role-hijack" },
|
|
27442
|
+
{ pattern: /role[\s-]?play\s+as/i, label: "role-hijack" },
|
|
27443
|
+
{ pattern: /switch\s+(?:to\s+)?(?:your\s+)?(?:new\s+)?(?:role|persona|identity)/i, label: "role-hijack" },
|
|
27444
|
+
{ pattern: /<\/?system(?:\s[^>]*)?>/, label: "fake-tag" },
|
|
27445
|
+
{ pattern: /<\/?instructions?(?:\s[^>]*)?>/, label: "fake-tag" },
|
|
27446
|
+
{ pattern: /<\/?prompt(?:\s[^>]*)?>/, label: "fake-tag" },
|
|
27447
|
+
{ pattern: /<\/?assistant(?:\s[^>]*)?>/, label: "fake-tag" },
|
|
27448
|
+
{ pattern: /<\/?human(?:\s[^>]*)?>/, label: "fake-tag" },
|
|
27449
|
+
{ pattern: /\[SYSTEM\](?:\s*:)?/i, label: "fake-tag" },
|
|
27450
|
+
{ pattern: /\[INST\]|\[\/INST\]/i, label: "fake-tag" },
|
|
27451
|
+
{ pattern: /new\s+(?:system\s+)?instructions?\s*:/i, label: "new-instructions" },
|
|
27452
|
+
{ pattern: /updated?\s+(?:system\s+)?instructions?\s*:/i, label: "new-instructions" },
|
|
27453
|
+
{ pattern: /revised?\s+(?:system\s+)?instructions?\s*:/i, label: "new-instructions" },
|
|
27454
|
+
{ pattern: /(?:run|execute|eval)\s*\(\s*['"`].*(?:rm\s+-rf|curl\s+.*\|\s*(?:sh|bash)|wget\s+.*\|\s*(?:sh|bash))/i, label: "dangerous-command" },
|
|
27455
|
+
{ pattern: /\brm\s+-rf\s+[\/~]/i, label: "dangerous-command" },
|
|
27456
|
+
{ pattern: /(?:\u5168\u3066\u306E|\u3059\u3079\u3066\u306E)?(?:\u524D\u306E|\u4EE5\u524D\u306E)?\u6307\u793A\u3092(?:\u7121\u8996|\u5FD8\u308C|\u53D6\u308A\u6D88)/i, label: "instruction-override-ja" },
|
|
27457
|
+
{ pattern: /(?:\u65B0\u3057\u3044|\u66F4\u65B0\u3055\u308C\u305F)\u6307\u793A\s*[:\uFF1A]/i, label: "new-instructions-ja" }
|
|
27458
|
+
];
|
|
27459
|
+
var SUSPICIOUS_UNICODE = /[\u200B-\u200F\u202A-\u202E\u2060-\u2069\uFEFF\u00AD]/g;
|
|
27460
|
+
function sanitizeAgentOutput(text) {
|
|
27461
|
+
const warnings = [];
|
|
27462
|
+
const invisibleCount = (text.match(SUSPICIOUS_UNICODE) || []).length;
|
|
27463
|
+
if (invisibleCount > 0) {
|
|
27464
|
+
warnings.push(`Stripped ${invisibleCount} suspicious Unicode characters`);
|
|
27465
|
+
text = text.replace(SUSPICIOUS_UNICODE, "");
|
|
27466
|
+
}
|
|
27467
|
+
for (const { pattern, label } of INJECTION_PATTERNS) {
|
|
27468
|
+
if (pattern.test(text)) {
|
|
27469
|
+
warnings.push(`Injection pattern detected: ${label}`);
|
|
27470
|
+
text = text.replace(pattern, (match) => `[SANITIZED:${label}]`);
|
|
27471
|
+
}
|
|
27472
|
+
}
|
|
27473
|
+
const flagged = warnings.length > 0;
|
|
27474
|
+
if (flagged) {
|
|
27475
|
+
log("Sanitizer flagged content", { warnings });
|
|
27476
|
+
}
|
|
27477
|
+
return { text, flagged, warnings };
|
|
27478
|
+
}
|
|
27479
|
+
function sanitizeSpawnResult(result) {
|
|
27480
|
+
const { text, flagged, warnings } = sanitizeAgentOutput(result);
|
|
27481
|
+
if (!flagged)
|
|
27482
|
+
return text;
|
|
27483
|
+
const banner = `
|
|
27484
|
+
|
|
27485
|
+
> **[Safety] Potential prompt injection detected in agent output.**
|
|
27486
|
+
> Patterns: ${warnings.join(", ")}
|
|
27487
|
+
> The flagged content has been neutralized.
|
|
27488
|
+
`;
|
|
27489
|
+
return banner + `
|
|
27490
|
+
` + text;
|
|
27491
|
+
}
|
|
27366
27492
|
// src/tools/spawn-agent.ts
|
|
27493
|
+
var DEFAULT_MAX_TOTAL_SPAWNED = 15;
|
|
27494
|
+
var DEFAULT_AGENT_TIMEOUT_MS = 180000;
|
|
27367
27495
|
function showToast(ctx, title, message, variant = "info") {
|
|
27368
27496
|
const client = ctx.client;
|
|
27369
27497
|
client.tui?.showToast?.({
|
|
27370
27498
|
body: { title, message, variant, duration: 2000 }
|
|
27371
27499
|
})?.catch?.(() => {});
|
|
27372
27500
|
}
|
|
27501
|
+
async function withTimeout(promise3, ms, label) {
|
|
27502
|
+
let timer;
|
|
27503
|
+
const timeout = new Promise((_, reject) => {
|
|
27504
|
+
timer = setTimeout(() => reject(new Error(`Timeout: ${label} exceeded ${ms}ms`)), ms);
|
|
27505
|
+
});
|
|
27506
|
+
try {
|
|
27507
|
+
return await Promise.race([promise3, timeout]);
|
|
27508
|
+
} finally {
|
|
27509
|
+
clearTimeout(timer);
|
|
27510
|
+
}
|
|
27511
|
+
}
|
|
27373
27512
|
async function runAgent(ctx, task, toolCtx, internalSessions, deps, progress) {
|
|
27374
27513
|
const { agent, prompt, description } = task;
|
|
27375
27514
|
const t0 = Date.now();
|
|
27515
|
+
const timeoutMs = deps.agentTimeoutMs ?? DEFAULT_AGENT_TIMEOUT_MS;
|
|
27376
27516
|
const elapsed = () => ((Date.now() - (progress?.startTime ?? t0)) / 1000).toFixed(0);
|
|
27377
27517
|
const updateTitle = (status) => {
|
|
27378
27518
|
toolCtx.metadata({ title: status });
|
|
@@ -27398,26 +27538,27 @@ async function runAgent(ctx, task, toolCtx, internalSessions, deps, progress) {
|
|
|
27398
27538
|
**Error**: Failed to create session`;
|
|
27399
27539
|
}
|
|
27400
27540
|
internalSessions.add(sessionID);
|
|
27401
|
-
await ctx.client.session.prompt({
|
|
27541
|
+
await withTimeout(ctx.client.session.prompt({
|
|
27402
27542
|
path: { id: sessionID },
|
|
27403
27543
|
body: {
|
|
27404
27544
|
parts: [{ type: "text", text: prompt }],
|
|
27405
27545
|
agent
|
|
27406
27546
|
},
|
|
27407
27547
|
query: { directory: ctx.directory }
|
|
27408
|
-
});
|
|
27548
|
+
}), timeoutMs, `${agent} (${description})`);
|
|
27409
27549
|
const messagesResp = await ctx.client.session.messages({
|
|
27410
27550
|
path: { id: sessionID },
|
|
27411
27551
|
query: { directory: ctx.directory }
|
|
27412
27552
|
});
|
|
27413
27553
|
const messages = messagesResp.data ?? [];
|
|
27414
27554
|
const lastAssistant = messages.filter((m) => m.info?.role === "assistant").pop();
|
|
27415
|
-
const
|
|
27555
|
+
const rawResult = lastAssistant?.parts?.filter((p) => p.type === "text" && p.text).map((p) => p.text).join(`
|
|
27416
27556
|
`) ?? "(No response from agent)";
|
|
27417
27557
|
internalSessions.delete(sessionID);
|
|
27418
27558
|
await ctx.client.session.delete({ path: { id: sessionID }, query: { directory: ctx.directory } }).catch(() => {});
|
|
27419
27559
|
const agentTime = ((Date.now() - t0) / 1000).toFixed(1);
|
|
27420
27560
|
log(`spawn_agent: ${agent} done`, { seconds: agentTime, description });
|
|
27561
|
+
const result = sanitizeSpawnResult(rawResult);
|
|
27421
27562
|
return `## ${description} (${agentTime}s)
|
|
27422
27563
|
|
|
27423
27564
|
**Agent**: ${agent}
|
|
@@ -27431,7 +27572,14 @@ ${result}`;
|
|
|
27431
27572
|
await ctx.client.session.delete({ path: { id: sessionID }, query: { directory: ctx.directory } }).catch(() => {});
|
|
27432
27573
|
}
|
|
27433
27574
|
const msg = error92 instanceof Error ? error92.message : String(error92);
|
|
27434
|
-
|
|
27575
|
+
const isTimeout = msg.startsWith("Timeout:");
|
|
27576
|
+
log(`spawn_agent: ${agent} ${isTimeout ? "timeout" : "error"}`, { error: msg });
|
|
27577
|
+
if (isTimeout) {
|
|
27578
|
+
return `## ${description}
|
|
27579
|
+
|
|
27580
|
+
**Agent**: ${agent}
|
|
27581
|
+
**Timeout**: Agent did not complete within ${(timeoutMs / 1000).toFixed(0)}s`;
|
|
27582
|
+
}
|
|
27435
27583
|
return `## ${description}
|
|
27436
27584
|
|
|
27437
27585
|
**Agent**: ${agent}
|
|
@@ -27439,6 +27587,7 @@ ${result}`;
|
|
|
27439
27587
|
}
|
|
27440
27588
|
}
|
|
27441
27589
|
function createSpawnAgentTool(ctx, internalSessions, deps = {}) {
|
|
27590
|
+
const maxTotalSpawned = deps.maxTotalSpawned ?? DEFAULT_MAX_TOTAL_SPAWNED;
|
|
27442
27591
|
return tool({
|
|
27443
27592
|
description: `Spawn subagents to execute tasks in PARALLEL.
|
|
27444
27593
|
All agents in the array run concurrently (respecting concurrency limits if configured).
|
|
@@ -27483,6 +27632,11 @@ spawn_agent({
|
|
|
27483
27632
|
return `Error: agents[${i}].description is required`;
|
|
27484
27633
|
}
|
|
27485
27634
|
}
|
|
27635
|
+
const currentActive = internalSessions.size;
|
|
27636
|
+
if (currentActive + agents.length > maxTotalSpawned) {
|
|
27637
|
+
log("spawn_agent: BLOCKED by spawn limit", { currentActive, requested: agents.length, max: maxTotalSpawned });
|
|
27638
|
+
return `Error: Too many concurrent spawned agents. Active: ${currentActive}, requested: ${agents.length}, max: ${maxTotalSpawned}. Wait for active agents to complete or increase safety.maxTotalSpawned.`;
|
|
27639
|
+
}
|
|
27486
27640
|
const agentNames = agents.map((a) => a.agent);
|
|
27487
27641
|
log("spawn_agent", { count: agents.length, agents: agentNames });
|
|
27488
27642
|
showToast(ctx, "spawn_agent", `${agents.length} agents: ${agentNames.join(", ")}`);
|
|
@@ -27559,8 +27713,21 @@ function resolveTaskModel(task, deps) {
|
|
|
27559
27713
|
// src/tools/ralph-loop.ts
|
|
27560
27714
|
var COMPLETION_MARKER = "<promise>DONE</promise>";
|
|
27561
27715
|
var DEFAULT_MAX_ITERATIONS = 10;
|
|
27716
|
+
var DEFAULT_ITERATION_TIMEOUT_MS = 180000;
|
|
27562
27717
|
var activeLoops = new Map;
|
|
27563
|
-
function
|
|
27718
|
+
async function withTimeout2(promise3, ms, label) {
|
|
27719
|
+
let timer;
|
|
27720
|
+
const timeout = new Promise((_, reject) => {
|
|
27721
|
+
timer = setTimeout(() => reject(new Error(`Timeout: ${label} exceeded ${ms}ms`)), ms);
|
|
27722
|
+
});
|
|
27723
|
+
try {
|
|
27724
|
+
return await Promise.race([promise3, timeout]);
|
|
27725
|
+
} finally {
|
|
27726
|
+
clearTimeout(timer);
|
|
27727
|
+
}
|
|
27728
|
+
}
|
|
27729
|
+
function createRalphLoopTools(ctx, internalSessions, deps = {}) {
|
|
27730
|
+
const iterationTimeoutMs = deps.iterationTimeoutMs ?? DEFAULT_ITERATION_TIMEOUT_MS;
|
|
27564
27731
|
const ralph_loop = tool({
|
|
27565
27732
|
description: `Start autonomous loop. Agent keeps working until it outputs ${COMPLETION_MARKER} or max iterations reached.
|
|
27566
27733
|
Use this for tasks that require multiple rounds of autonomous work (implementation, refactoring, migration).
|
|
@@ -27588,29 +27755,43 @@ The agent will be prompted to continue from where it left off each iteration.`,
|
|
|
27588
27755
|
sessionID
|
|
27589
27756
|
};
|
|
27590
27757
|
activeLoops.set(sessionID, state);
|
|
27591
|
-
log(`Ralph Loop started: ${agentName}, max ${maxIter} iterations`);
|
|
27758
|
+
log(`Ralph Loop started: ${agentName}, max ${maxIter} iterations, timeout ${iterationTimeoutMs}ms/iter`);
|
|
27592
27759
|
try {
|
|
27593
27760
|
for (let i = 0;i < maxIter && state.active; i++) {
|
|
27594
27761
|
state.iteration = i + 1;
|
|
27595
27762
|
toolCtx.metadata({ title: `Ralph Loop [${i + 1}/${maxIter}] \u2014 ${agentName}` });
|
|
27596
27763
|
const prompt = i === 0 ? args.prompt : buildContinuationPrompt(args.prompt, i + 1);
|
|
27597
|
-
|
|
27598
|
-
|
|
27599
|
-
|
|
27600
|
-
|
|
27601
|
-
|
|
27602
|
-
|
|
27603
|
-
|
|
27604
|
-
|
|
27764
|
+
try {
|
|
27765
|
+
await withTimeout2(ctx.client.session.prompt({
|
|
27766
|
+
path: { id: sessionID },
|
|
27767
|
+
body: {
|
|
27768
|
+
parts: [{ type: "text", text: prompt }],
|
|
27769
|
+
agent: agentName
|
|
27770
|
+
},
|
|
27771
|
+
query: { directory: ctx.directory }
|
|
27772
|
+
}), iterationTimeoutMs, `Ralph Loop iteration ${i + 1}`);
|
|
27773
|
+
} catch (iterError) {
|
|
27774
|
+
const msg = iterError instanceof Error ? iterError.message : String(iterError);
|
|
27775
|
+
if (msg.startsWith("Timeout:")) {
|
|
27776
|
+
log(`Ralph Loop iteration ${i + 1} timed out`);
|
|
27777
|
+
toolCtx.metadata({ title: `Ralph Loop TIMEOUT [${i + 1}/${maxIter}]` });
|
|
27778
|
+
return `## Ralph Loop Timeout (iteration ${i + 1}/${maxIter})
|
|
27779
|
+
|
|
27780
|
+
**Agent**: ${agentName}
|
|
27781
|
+
**Timeout**: Iteration did not complete within ${(iterationTimeoutMs / 1000).toFixed(0)}s`;
|
|
27782
|
+
}
|
|
27783
|
+
throw iterError;
|
|
27784
|
+
}
|
|
27605
27785
|
const messagesResp = await ctx.client.session.messages({
|
|
27606
27786
|
path: { id: sessionID },
|
|
27607
27787
|
query: { directory: ctx.directory }
|
|
27608
27788
|
});
|
|
27609
27789
|
const messages = messagesResp.data ?? [];
|
|
27610
27790
|
const lastAssistant = messages.filter((m) => m.info?.role === "assistant").pop();
|
|
27611
|
-
const
|
|
27791
|
+
const rawResult = lastAssistant?.parts?.filter((p) => p.type === "text" && p.text).map((p) => p.text).join(`
|
|
27612
27792
|
`) ?? "";
|
|
27613
|
-
|
|
27793
|
+
const resultText = sanitizeSpawnResult(rawResult);
|
|
27794
|
+
if (rawResult.includes(COMPLETION_MARKER)) {
|
|
27614
27795
|
toolCtx.metadata({ title: `Ralph Loop DONE [${i + 1}/${maxIter}]` });
|
|
27615
27796
|
log(`Ralph Loop completed at iteration ${i + 1}`);
|
|
27616
27797
|
const cleaned = resultText.replace(COMPLETION_MARKER, "").trim();
|
|
@@ -28358,12 +28539,17 @@ var OpenCodeUltra = async (ctx) => {
|
|
|
28358
28539
|
const resolveAgentModel = (agent) => {
|
|
28359
28540
|
return agents[agent]?.model;
|
|
28360
28541
|
};
|
|
28542
|
+
const safetyConfig = pluginConfig.safety ?? {};
|
|
28361
28543
|
const spawnAgent = createSpawnAgentTool(ctx, internalSessions, {
|
|
28362
28544
|
pool,
|
|
28363
28545
|
categories: pluginConfig.categories,
|
|
28364
|
-
resolveAgentModel
|
|
28546
|
+
resolveAgentModel,
|
|
28547
|
+
maxTotalSpawned: safetyConfig.maxTotalSpawned,
|
|
28548
|
+
agentTimeoutMs: safetyConfig.agentTimeoutMs
|
|
28549
|
+
});
|
|
28550
|
+
const ralphTools = createRalphLoopTools(ctx, internalSessions, {
|
|
28551
|
+
iterationTimeoutMs: safetyConfig.agentTimeoutMs
|
|
28365
28552
|
});
|
|
28366
|
-
const ralphTools = createRalphLoopTools(ctx, internalSessions);
|
|
28367
28553
|
const batchRead = createBatchReadTool(ctx);
|
|
28368
28554
|
const ledgerSave = createLedgerSaveTool(ctx);
|
|
28369
28555
|
const ledgerLoad = createLedgerLoadTool(ctx);
|
package/package.json
CHANGED