open-agents-ai 0.187.526 → 0.187.528

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 CHANGED
@@ -4842,10 +4842,10 @@ Try broader terms or use memory_read with a specific topic.`,
4842
4842
  const data = JSON.parse(raw);
4843
4843
  const topic = basename(file, ".json");
4844
4844
  for (const [key, entry] of Object.entries(data)) {
4845
- const dedup = `${topic}:${key}`;
4846
- if (seen.has(dedup))
4845
+ const dedup2 = `${topic}:${key}`;
4846
+ if (seen.has(dedup2))
4847
4847
  continue;
4848
- seen.add(dedup);
4848
+ seen.add(dedup2);
4849
4849
  if (entry?.value) {
4850
4850
  entries.push({
4851
4851
  topic,
@@ -524388,6 +524388,174 @@ var init_streaming_executor = __esm({
524388
524388
  }
524389
524389
  });
524390
524390
 
524391
+ // packages/orchestrator/dist/specDecomposer.js
524392
+ function isSourceModule(path8) {
524393
+ if (!path8 || path8.includes(".."))
524394
+ return false;
524395
+ const cleaned = path8.trim().replace(/[`"',]+$/, "").replace(/\s+#.*$/, "");
524396
+ if (!cleaned)
524397
+ return false;
524398
+ const dot = cleaned.lastIndexOf(".");
524399
+ if (dot < 0)
524400
+ return false;
524401
+ const ext = cleaned.slice(dot).toLowerCase();
524402
+ if (!SOURCE_EXTENSIONS.has(ext))
524403
+ return false;
524404
+ for (const re of EXCLUDE_PATTERNS) {
524405
+ if (re.test(cleaned))
524406
+ return false;
524407
+ }
524408
+ return true;
524409
+ }
524410
+ function extractFromTree(text) {
524411
+ const lineRe = /^([ \t│]*)([├└])──\s+(\S+)/gm;
524412
+ const out = [];
524413
+ const parents = [];
524414
+ let m2;
524415
+ while ((m2 = lineRe.exec(text)) !== null) {
524416
+ const prefix = m2[1];
524417
+ const token = m2[3].replace(/\/+$/, "");
524418
+ const depth = Math.floor(prefix.length / 4);
524419
+ parents.length = depth;
524420
+ const originalEndedWithSlash = m2[3].endsWith("/");
524421
+ const hasExtension = token.includes(".") && !token.endsWith(".");
524422
+ const isDir2 = originalEndedWithSlash || !hasExtension;
524423
+ if (isDir2) {
524424
+ parents[depth] = token;
524425
+ continue;
524426
+ }
524427
+ const segments = parents.filter((p2) => p2 && p2.length > 0);
524428
+ const fullPath = segments.length > 0 ? segments.join("/") + "/" + token : token;
524429
+ if (isSourceModule(fullPath)) {
524430
+ out.push({ path: fullPath });
524431
+ }
524432
+ }
524433
+ return out;
524434
+ }
524435
+ function extractFromModuleSections(text) {
524436
+ const re = /^#{2,4}\s+(?:Module|File):\s*[`"']?([^\s`"'\n]+)/gm;
524437
+ const out = [];
524438
+ let m2;
524439
+ while ((m2 = re.exec(text)) !== null) {
524440
+ const raw = m2[1].replace(/\/+$/, "");
524441
+ if (isSourceModule(raw)) {
524442
+ out.push({ path: raw });
524443
+ }
524444
+ }
524445
+ return out;
524446
+ }
524447
+ function extractFromHeaderList(text) {
524448
+ const re = /^#{3,4}\s+(?:Module:\s+)?[`"']?(src\/[^\s`"'\n]+\.[A-Za-z0-9]+)[`"']?/gm;
524449
+ const out = [];
524450
+ let m2;
524451
+ while ((m2 = re.exec(text)) !== null) {
524452
+ const raw = m2[1].replace(/\/+$/, "");
524453
+ if (isSourceModule(raw)) {
524454
+ out.push({ path: raw });
524455
+ }
524456
+ }
524457
+ return out;
524458
+ }
524459
+ function decomposeSpec(taskText) {
524460
+ if (!taskText || typeof taskText !== "string") {
524461
+ return { modules: [], strategy: "none" };
524462
+ }
524463
+ const tree2 = dedup(extractFromTree(taskText));
524464
+ if (tree2.length >= 3)
524465
+ return { modules: tree2, strategy: "tree" };
524466
+ const modSections = dedup(extractFromModuleSections(taskText));
524467
+ if (modSections.length >= 3)
524468
+ return { modules: modSections, strategy: "module-sections" };
524469
+ const headers = dedup(extractFromHeaderList(taskText));
524470
+ if (headers.length >= 3)
524471
+ return { modules: headers, strategy: "module-headers" };
524472
+ return { modules: [], strategy: "none" };
524473
+ }
524474
+ function dedup(items) {
524475
+ const seen = /* @__PURE__ */ new Set();
524476
+ const out = [];
524477
+ for (const e2 of items) {
524478
+ if (seen.has(e2.path))
524479
+ continue;
524480
+ seen.add(e2.path);
524481
+ out.push(e2);
524482
+ }
524483
+ return out;
524484
+ }
524485
+ function buildDecompositionDirective(modules, strategy) {
524486
+ const moduleList = modules.slice(0, 30).map((m2, i2) => ` ${i2 + 1}. ${m2.path}`).join("\n");
524487
+ const more = modules.length > 30 ? `
524488
+ ... +${modules.length - 30} more` : "";
524489
+ return [
524490
+ `[SPEC DECOMPOSED — ${modules.length} modules detected via ${strategy}]`,
524491
+ ``,
524492
+ `Your task spec contains a clear module structure. To stay within context budget on a multi-module implementation, complete each module via sub_agent rather than in main context.`,
524493
+ ``,
524494
+ `Detected modules:`,
524495
+ moduleList,
524496
+ more,
524497
+ ``,
524498
+ `Recommended workflow:`,
524499
+ ` 1. Call todo_write with one todo per module (the orchestrator pre-populated this for you).`,
524500
+ ` 2. For EACH module, mark its todo in_progress, then call:`,
524501
+ ` sub_agent({ subagent_type: "general", prompt: "Implement <module-path> per spec",`,
524502
+ ` relevantFiles: [{path:"<module-path>", content:"<existing or empty>"}], ... })`,
524503
+ ` 3. When sub_agent returns, mark the todo completed.`,
524504
+ ` 4. After ALL module todos are done, run the project's build/tests in main context.`,
524505
+ ` 5. If build/tests pass: call task_complete. If they fail: dispatch fix-up sub_agents for the failing module(s).`,
524506
+ ``,
524507
+ `Why this matters: implementing N modules in one context burns token budget linearly with N. sub_agent gives each module its own context window so the main context stays small for orchestration + integration verification.`,
524508
+ ``,
524509
+ `If you genuinely cannot use sub_agent for a module (e.g. it's a 3-line config file), inline editing in main context is fine — but for substantive modules, delegate.`
524510
+ ].filter(Boolean).join("\n");
524511
+ }
524512
+ function buildDecompositionTodos(modules) {
524513
+ return modules.map((m2, i2) => ({
524514
+ id: `decomp-mod-${i2 + 1}`,
524515
+ content: `Implement ${m2.path} (use sub_agent for focused context)`,
524516
+ status: "pending"
524517
+ }));
524518
+ }
524519
+ var SOURCE_EXTENSIONS, EXCLUDE_PATTERNS;
524520
+ var init_specDecomposer = __esm({
524521
+ "packages/orchestrator/dist/specDecomposer.js"() {
524522
+ "use strict";
524523
+ SOURCE_EXTENSIONS = /* @__PURE__ */ new Set([
524524
+ ".ts",
524525
+ ".tsx",
524526
+ ".js",
524527
+ ".jsx",
524528
+ ".mjs",
524529
+ ".cjs",
524530
+ ".py",
524531
+ ".pyi",
524532
+ ".rs",
524533
+ ".go",
524534
+ ".java",
524535
+ ".kt",
524536
+ ".c",
524537
+ ".cc",
524538
+ ".cpp",
524539
+ ".h",
524540
+ ".hpp",
524541
+ ".rb",
524542
+ ".php",
524543
+ ".swift",
524544
+ ".scala",
524545
+ ".clj",
524546
+ ".lua"
524547
+ ]);
524548
+ EXCLUDE_PATTERNS = [
524549
+ // Excluded directories — match anywhere in path (start-or-slash boundary).
524550
+ /(^|\/)(node_modules|dist|build|out|target|\.git|\.next|coverage|__pycache__|venv|\.venv)\//,
524551
+ // Test fixtures + samples — keep src/ tests but drop fixture/sample dirs.
524552
+ /(^|\/)(fixtures|samples)\//,
524553
+ // Package metadata / config — caller doesn't want decomposition for these.
524554
+ /(^|\/)(package\.json|package-lock\.json|yarn\.lock|pnpm-lock\.yaml|tsconfig\.json|jest\.config\.[jt]s|vitest\.config\.[jt]s|\.eslintrc\.[a-z]+|Cargo\.toml|Cargo\.lock|go\.mod|go\.sum|pyproject\.toml|requirements\.txt|setup\.py|\.gitignore|\.npmignore|README\.md|LICENSE)$/
524555
+ ];
524556
+ }
524557
+ });
524558
+
524391
524559
  // packages/orchestrator/dist/preflightSnapshot.js
524392
524560
  var preflightSnapshot_exports = {};
524393
524561
  __export(preflightSnapshot_exports, {
@@ -525432,7 +525600,7 @@ RECOVERY: cd to the directory containing '${file}', run a plain install with no
525432
525600
  });
525433
525601
 
525434
525602
  // packages/orchestrator/dist/agenticRunner.js
525435
- import { existsSync as _fsExistsSync, readFileSync as _fsReadFileSync } from "node:fs";
525603
+ import { existsSync as _fsExistsSync, readFileSync as _fsReadFileSync, writeFileSync as _fsWriteFileSync, mkdirSync as _fsMkdirSync } from "node:fs";
525436
525604
  import { join as _pathJoin } from "node:path";
525437
525605
  import { homedir as _osHomedir } from "node:os";
525438
525606
  import { z as z15 } from "zod";
@@ -525665,6 +525833,7 @@ var init_agenticRunner = __esm({
525665
525833
  init_hooks();
525666
525834
  init_app_state();
525667
525835
  init_streaming_executor();
525836
+ init_specDecomposer();
525668
525837
  TOOL_SUBSETS = {
525669
525838
  web: ["web_search", "web_fetch", "web_crawl"],
525670
525839
  code: ["file_patch", "file_explore", "batch_edit", "file_read", "file_write", "file_edit"],
@@ -526005,6 +526174,18 @@ var init_agenticRunner = __esm({
526005
526174
  //
526006
526175
  // Kill switch: OA_DISABLE_REG61_COERCE=1 disables BOTH set and enforce.
526007
526176
  _reg61PerpetualGateActive = false;
526177
+ // DECOMP-2 (root-cause from batch531-midi-decomp, 2026-05-03): compelling
526178
+ // sub_agent delegation. DECOMP-1's informational directive was ignored
526179
+ // (0 sub_agent calls in 466 tool-call run despite directive at turn 1).
526180
+ // Mirrors the BFC-61.G escalation arc: when the agent has edited
526181
+ // ≥THRESHOLD distinct files in main context WITHOUT calling sub_agent,
526182
+ // the dispatcher BLOCKS edits to NEW files (paths not yet edited) until
526183
+ // sub_agent is invoked. Edits to already-touched files are still allowed
526184
+ // (current-module finishing work). sub_agent dispatch clears the gate.
526185
+ // Kill switch: OA_DISABLE_DECOMP2=1.
526186
+ _decomp2MainContextFiles = /* @__PURE__ */ new Set();
526187
+ _decomp2SubAgentCalls = 0;
526188
+ _decomp2GateActive = false;
526008
526189
  // MEM_PATH item #9: adaptive retrieval cache. When the (goalHash, recent-tool-sig)
526009
526190
  // hasn't changed since last retrieval, skip the PPR call entirely and reuse
526010
526191
  // the previous memoryLines.
@@ -528335,6 +528516,9 @@ Respond with your assessment, then take action.`;
528335
528516
  this._aborting = false;
528336
528517
  this._reg61CooldownUntilTurn = -1;
528337
528518
  this._reg61PerpetualGateActive = false;
528519
+ this._decomp2MainContextFiles = /* @__PURE__ */ new Set();
528520
+ this._decomp2SubAgentCalls = 0;
528521
+ this._decomp2GateActive = false;
528338
528522
  if (!globalThis.__oa_rca1_sigterm_installed) {
528339
528523
  globalThis.__oa_rca1_sigterm_installed = true;
528340
528524
  const _sigtermHandler = () => {
@@ -528474,6 +528658,46 @@ TASK: ${task}` : task;
528474
528658
  { role: "system", content: systemPrompt },
528475
528659
  { role: "user", content: userContent }
528476
528660
  ];
528661
+ if (process.env["OA_DISABLE_DECOMP1"] !== "1") {
528662
+ try {
528663
+ const _taskBodyForDecomp = typeof userContent === "string" ? userContent : "";
528664
+ const _decomp = decomposeSpec(_taskBodyForDecomp);
528665
+ if (_decomp.modules.length > 0) {
528666
+ const _directive = buildDecompositionDirective(_decomp.modules, _decomp.strategy);
528667
+ messages2.push({ role: "system", content: _directive });
528668
+ const _todos = buildDecompositionTodos(_decomp.modules);
528669
+ try {
528670
+ const sid = process.env["OA_SESSION_ID"] || this._sessionId || "default";
528671
+ const safe = sid.replace(/[^a-zA-Z0-9_.-]/g, "_");
528672
+ const fp = _pathJoin(_osHomedir(), ".open-agents", "todos", `${safe}.json`);
528673
+ const dir = _pathJoin(_osHomedir(), ".open-agents", "todos");
528674
+ try {
528675
+ _fsMkdirSync(dir, { recursive: true });
528676
+ } catch {
528677
+ }
528678
+ let _existing = [];
528679
+ try {
528680
+ if (_fsExistsSync(fp)) {
528681
+ const _parsed = JSON.parse(_fsReadFileSync(fp, "utf-8"));
528682
+ if (Array.isArray(_parsed))
528683
+ _existing = _parsed;
528684
+ }
528685
+ } catch {
528686
+ }
528687
+ if (_existing.length === 0) {
528688
+ _fsWriteFileSync(fp, JSON.stringify(_todos, null, 2), "utf-8");
528689
+ }
528690
+ } catch {
528691
+ }
528692
+ this.emit({
528693
+ type: "status",
528694
+ content: `DECOMP-1 SPEC DECOMPOSED — ${_decomp.modules.length} modules detected via ${_decomp.strategy}; directive injected + todos pre-populated`,
528695
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
528696
+ });
528697
+ }
528698
+ } catch {
528699
+ }
528700
+ }
528477
528701
  try {
528478
528702
  if (this.options.subAgent)
528479
528703
  throw "skip-handoff-subagent";
@@ -530379,7 +530603,18 @@ ${memoryLines.join("\n")}`
530379
530603
  // agents used to ignore REG-61.
530380
530604
  "web_search",
530381
530605
  "task_complete",
530382
- "ask_user"
530606
+ "ask_user",
530607
+ // DECOMP-1: sub-agent dispatch is productive work (it spawns
530608
+ // a focused-context implementation worker). Block-listing
530609
+ // sub_agent here would defeat the spec-decomposition pattern
530610
+ // — agents would be forced into main-context edits even when
530611
+ // delegation is the right move. sub_agent calls do NOT clear
530612
+ // the gate (we want the agent to also produce direct edits
530613
+ // for orchestration glue / integration), but they're allowed
530614
+ // through.
530615
+ "sub_agent",
530616
+ "priority_delegate",
530617
+ "background_run"
530383
530618
  ]);
530384
530619
  if (REG61_EDIT_TOOLS.has(tc.name) && this._reg61PerpetualGateActive) {
530385
530620
  this._reg61PerpetualGateActive = false;
@@ -530434,6 +530669,71 @@ ${memoryLines.join("\n")}`
530434
530669
  });
530435
530670
  return { tc, output: reg61BlockMsg };
530436
530671
  }
530672
+ if (this._decomp2GateActive && process.env["OA_DISABLE_DECOMP2"] !== "1") {
530673
+ const DECOMP2_EDIT_TOOLS = /* @__PURE__ */ new Set([
530674
+ "file_write",
530675
+ "file_edit",
530676
+ "batch_edit",
530677
+ "file_patch"
530678
+ ]);
530679
+ if (DECOMP2_EDIT_TOOLS.has(tc.name)) {
530680
+ const _editPath = tc.arguments?.["path"] ?? "";
530681
+ const _isNewFile = _editPath && !this._decomp2MainContextFiles.has(_editPath);
530682
+ if (_isNewFile) {
530683
+ this.emit({
530684
+ type: "tool_call",
530685
+ toolName: tc.name,
530686
+ toolArgs: tc.arguments,
530687
+ turn,
530688
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
530689
+ });
530690
+ const _filesList = [...this._decomp2MainContextFiles].slice(0, 8).map((p2) => ` • ${p2}`).join("\n");
530691
+ const _moreFiles = this._decomp2MainContextFiles.size > 8 ? `
530692
+ ... +${this._decomp2MainContextFiles.size - 8} more` : "";
530693
+ const decomp2Msg = [
530694
+ `[BLOCKED — DECOMP-2 main-context exhaustion]`,
530695
+ ``,
530696
+ `You have already edited ${this._decomp2MainContextFiles.size} distinct files in main context without invoking sub_agent. Continuing to edit ANOTHER new file ('${_editPath}') will keep your context window saturated and trigger compaction thrashing.`,
530697
+ ``,
530698
+ `Files you've already edited (will accept further edits to these):`,
530699
+ _filesList,
530700
+ _moreFiles,
530701
+ ``,
530702
+ `For the next module, you MUST use sub_agent. Workflow:`,
530703
+ ` 1. Pick a pending todo (the orchestrator pre-populated module todos via DECOMP-1).`,
530704
+ ` 2. Call sub_agent({`,
530705
+ ` subagent_type: "general",`,
530706
+ ` prompt: "Implement <module-path> per spec",`,
530707
+ ` relevantFiles: [{path: "<module-path>", content: "" /* or existing content */}],`,
530708
+ ` constraints: ["Stay within <module-path>; integration verification happens later"],`,
530709
+ ` })`,
530710
+ ` 3. After sub_agent returns, mark the todo completed.`,
530711
+ ``,
530712
+ `Why this matters: spreading edits across N files in main context burns ~N × file_size tokens. sub_agent gives the next module a focused context window.`,
530713
+ ``,
530714
+ `If you have ALREADY edited '${_editPath}' (this is a continuation), the orchestrator's set must have missed it — call file_read to verify, then re-edit. Otherwise, dispatch sub_agent now.`
530715
+ ].join("\n");
530716
+ this.emit({
530717
+ type: "tool_result",
530718
+ toolName: tc.name,
530719
+ success: false,
530720
+ content: decomp2Msg.slice(0, 120),
530721
+ turn,
530722
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
530723
+ });
530724
+ this.emit({
530725
+ type: "status",
530726
+ content: `DECOMP-2 NEW-FILE BLOCK — rejected ${tc.name}('${_editPath}') at turn ${turn}; gate stays active until sub_agent is invoked`,
530727
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
530728
+ });
530729
+ this._tagSyntheticFailure({
530730
+ mode: "step_repetition",
530731
+ rationale: `DECOMP-2 new-file block on '${tc.name}'(${_editPath}) — agent has spread edits across ${this._decomp2MainContextFiles.size} files without sub_agent`
530732
+ });
530733
+ return { tc, output: decomp2Msg };
530734
+ }
530735
+ }
530736
+ }
530437
530737
  const PROGRESS_GATE_BYPASS_TOOLS = /* @__PURE__ */ new Set([
530438
530738
  "todo_write",
530439
530739
  "todo_read",
@@ -531541,6 +531841,30 @@ Respond with EXACTLY this structure before your next tool call:
531541
531841
  if (this._fileWriteTimestamps.length > 200) {
531542
531842
  this._fileWriteTimestamps.shift();
531543
531843
  }
531844
+ const _editPath = tc.arguments?.["path"] ?? "";
531845
+ if (_editPath && typeof _editPath === "string") {
531846
+ this._decomp2MainContextFiles.add(_editPath);
531847
+ const DECOMP2_FILE_SPREAD_THRESHOLD = 5;
531848
+ if (!this._decomp2GateActive && this._decomp2MainContextFiles.size >= DECOMP2_FILE_SPREAD_THRESHOLD && this._decomp2SubAgentCalls === 0 && process.env["OA_DISABLE_DECOMP2"] !== "1") {
531849
+ this._decomp2GateActive = true;
531850
+ this.emit({
531851
+ type: "status",
531852
+ content: `DECOMP-2 NEW-FILE GATE ACTIVATED — ${this._decomp2MainContextFiles.size} distinct files edited in main context, 0 sub_agent calls; further edits to NEW files will be blocked until sub_agent is invoked`,
531853
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
531854
+ });
531855
+ }
531856
+ }
531857
+ }
531858
+ }
531859
+ if (tc.name === "sub_agent" || tc.name === "priority_delegate" || tc.name === "background_run") {
531860
+ this._decomp2SubAgentCalls++;
531861
+ if (this._decomp2GateActive) {
531862
+ this._decomp2GateActive = false;
531863
+ this.emit({
531864
+ type: "status",
531865
+ content: `DECOMP-2 GATE CLEARED — '${tc.name}' satisfied delegation directive at turn ${turn}`,
531866
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
531867
+ });
531544
531868
  }
531545
531869
  }
531546
531870
  if (result && result.success === false) {
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "open-agents-ai",
3
- "version": "0.187.526",
3
+ "version": "0.187.528",
4
4
  "lockfileVersion": 3,
5
5
  "requires": true,
6
6
  "packages": {
7
7
  "": {
8
8
  "name": "open-agents-ai",
9
- "version": "0.187.526",
9
+ "version": "0.187.528",
10
10
  "hasInstallScript": true,
11
11
  "license": "CC-BY-NC-4.0",
12
12
  "dependencies": {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "open-agents-ai",
3
- "version": "0.187.526",
3
+ "version": "0.187.528",
4
4
  "description": "AI coding agent powered by open-source models (Ollama/vLLM) — interactive TUI with agentic tool-calling loop",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",