opencode-ultra 0.3.0 → 0.4.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 CHANGED
@@ -14369,6 +14369,49 @@ function log(message, data) {
14369
14369
  console.error(`${PREFIX} ${message}`);
14370
14370
  }
14371
14371
  }
14372
+ // src/shared/ttl-map.ts
14373
+ class TtlMap {
14374
+ map = new Map;
14375
+ maxSize;
14376
+ ttlMs;
14377
+ constructor(opts = {}) {
14378
+ this.maxSize = opts.maxSize ?? 1000;
14379
+ this.ttlMs = opts.ttlMs ?? 0;
14380
+ }
14381
+ get(key) {
14382
+ const entry = this.map.get(key);
14383
+ if (!entry)
14384
+ return;
14385
+ if (this.ttlMs > 0 && Date.now() - entry.createdAt > this.ttlMs) {
14386
+ this.map.delete(key);
14387
+ return;
14388
+ }
14389
+ return entry.value;
14390
+ }
14391
+ set(key, value) {
14392
+ if (this.map.has(key)) {
14393
+ this.map.delete(key);
14394
+ }
14395
+ if (this.map.size >= this.maxSize) {
14396
+ const oldest = this.map.keys().next().value;
14397
+ if (oldest !== undefined)
14398
+ this.map.delete(oldest);
14399
+ }
14400
+ this.map.set(key, { value, createdAt: Date.now() });
14401
+ }
14402
+ has(key) {
14403
+ return this.get(key) !== undefined;
14404
+ }
14405
+ delete(key) {
14406
+ return this.map.delete(key);
14407
+ }
14408
+ get size() {
14409
+ return this.map.size;
14410
+ }
14411
+ clear() {
14412
+ this.map.clear();
14413
+ }
14414
+ }
14372
14415
  // src/config.ts
14373
14416
  var AgentOverrideSchema = exports_external.object({
14374
14417
  model: exports_external.string().optional(),
@@ -14396,6 +14439,11 @@ var CategoryConfigSchema = exports_external.object({
14396
14439
  var PluginConfigSchema = exports_external.object({
14397
14440
  agents: AgentOverridesSchema.optional(),
14398
14441
  categories: exports_external.record(exports_external.string(), CategoryConfigSchema).optional(),
14442
+ fragments: exports_external.record(exports_external.string(), exports_external.array(exports_external.string())).optional(),
14443
+ prompt_renderer: exports_external.object({
14444
+ default: exports_external.enum(["markdown", "xml", "json"]).optional(),
14445
+ model_overrides: exports_external.record(exports_external.string(), exports_external.enum(["markdown", "xml", "json"])).optional()
14446
+ }).optional(),
14399
14447
  disabled_agents: exports_external.array(exports_external.string()).optional(),
14400
14448
  disabled_hooks: exports_external.array(exports_external.string()).optional(),
14401
14449
  disabled_tools: exports_external.array(exports_external.string()).optional(),
@@ -14819,7 +14867,7 @@ var THINK_MESSAGE = `Extended thinking enabled. Take your time to reason thoroug
14819
14867
  // src/hooks/rules-injector.ts
14820
14868
  import * as fs2 from "fs";
14821
14869
  import * as path2 from "path";
14822
- var cache = new Map;
14870
+ var cache = new TtlMap({ maxSize: 50, ttlMs: 10 * 60 * 1000 });
14823
14871
  function loadCachedFile(filePath) {
14824
14872
  try {
14825
14873
  if (!fs2.existsSync(filePath))
@@ -14874,6 +14922,94 @@ function loadCodeStyle(projectDir) {
14874
14922
  return hit.content;
14875
14923
  }
14876
14924
 
14925
+ // src/hooks/fragment-injector.ts
14926
+ import * as fs3 from "fs";
14927
+ import * as path3 from "path";
14928
+ var cache2 = new TtlMap({ maxSize: 100, ttlMs: 10 * 60 * 1000 });
14929
+ async function readCached(absPath) {
14930
+ try {
14931
+ const st = await fs3.promises.stat(absPath);
14932
+ if (!st.isFile())
14933
+ return null;
14934
+ const prev = cache2.get(absPath);
14935
+ if (prev && prev.mtimeMs === st.mtimeMs)
14936
+ return prev.content;
14937
+ const content = await fs3.promises.readFile(absPath, "utf-8");
14938
+ cache2.set(absPath, { mtimeMs: st.mtimeMs, content });
14939
+ return content;
14940
+ } catch (err) {
14941
+ if (err?.code !== "ENOENT") {
14942
+ log("fragment read failed", { path: absPath, error: String(err) });
14943
+ }
14944
+ return null;
14945
+ }
14946
+ }
14947
+ function createFragmentInjector(ctx, internalSessions, fragments) {
14948
+ return async (input, output) => {
14949
+ if (input.sessionID && internalSessions.has(input.sessionID))
14950
+ return;
14951
+ const agent = input.agent;
14952
+ if (!agent || !fragments)
14953
+ return;
14954
+ const fragmentPaths = fragments[agent];
14955
+ if (!Array.isArray(fragmentPaths) || fragmentPaths.length === 0)
14956
+ return;
14957
+ for (const rel of fragmentPaths) {
14958
+ const abs = path3.resolve(ctx.directory, rel);
14959
+ const content = await readCached(abs);
14960
+ if (content === null) {
14961
+ log("fragment file not found; skipping", { agent, path: rel });
14962
+ continue;
14963
+ }
14964
+ output.system.push(content);
14965
+ }
14966
+ };
14967
+ }
14968
+
14969
+ // src/hooks/prompt-renderer.ts
14970
+ function escapeXml(s) {
14971
+ return s.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/\"/g, "&quot;").replace(/'/g, "&apos;");
14972
+ }
14973
+ function resolvePromptFormat(inputModel, config2) {
14974
+ const def = config2?.default ?? "markdown";
14975
+ const overrides = config2?.model_overrides ?? {};
14976
+ const providerID = inputModel?.providerID;
14977
+ const modelID = inputModel?.modelID;
14978
+ const key = typeof providerID === "string" && typeof modelID === "string" ? `${providerID}/${modelID}` : undefined;
14979
+ if (key && overrides[key])
14980
+ return overrides[key];
14981
+ if (typeof modelID === "string" && overrides[modelID])
14982
+ return overrides[modelID];
14983
+ return def;
14984
+ }
14985
+ function formatSystemSection(format2, content) {
14986
+ if (format2 === "markdown")
14987
+ return content;
14988
+ if (format2 === "xml") {
14989
+ return `<section name="Rules">${escapeXml(content)}</section>`;
14990
+ }
14991
+ if (format2 === "json") {
14992
+ return JSON.stringify({ section: "Rules", content });
14993
+ }
14994
+ return content;
14995
+ }
14996
+ function createPromptRendererHook(internalSessions, config2) {
14997
+ return (input, output) => {
14998
+ if (input.sessionID && internalSessions.has(input.sessionID))
14999
+ return;
15000
+ if (!Array.isArray(output.system) || output.system.length === 0)
15001
+ return;
15002
+ const format2 = resolvePromptFormat(input.model, config2);
15003
+ if (format2 === "markdown")
15004
+ return;
15005
+ try {
15006
+ output.system = output.system.map((s) => formatSystemSection(format2, s));
15007
+ } catch (err) {
15008
+ log("prompt renderer failed", { error: String(err) });
15009
+ }
15010
+ };
15011
+ }
15012
+
14877
15013
  // node_modules/@opencode-ai/plugin/node_modules/zod/v4/classic/external.js
14878
15014
  var exports_external2 = {};
14879
15015
  __export(exports_external2, {
@@ -15603,15 +15739,15 @@ function mergeDefs2(...defs) {
15603
15739
  function cloneDef2(schema) {
15604
15740
  return mergeDefs2(schema._zod.def);
15605
15741
  }
15606
- function getElementAtPath2(obj, path3) {
15607
- if (!path3)
15742
+ function getElementAtPath2(obj, path4) {
15743
+ if (!path4)
15608
15744
  return obj;
15609
- return path3.reduce((acc, key) => acc?.[key], obj);
15745
+ return path4.reduce((acc, key) => acc?.[key], obj);
15610
15746
  }
15611
15747
  function promiseAllObject2(promisesObj) {
15612
15748
  const keys = Object.keys(promisesObj);
15613
- const promises = keys.map((key) => promisesObj[key]);
15614
- return Promise.all(promises).then((results) => {
15749
+ const promises2 = keys.map((key) => promisesObj[key]);
15750
+ return Promise.all(promises2).then((results) => {
15615
15751
  const resolvedObj = {};
15616
15752
  for (let i = 0;i < keys.length; i++) {
15617
15753
  resolvedObj[keys[i]] = results[i];
@@ -15965,11 +16101,11 @@ function aborted2(x, startIndex = 0) {
15965
16101
  }
15966
16102
  return false;
15967
16103
  }
15968
- function prefixIssues2(path3, issues) {
16104
+ function prefixIssues2(path4, issues) {
15969
16105
  return issues.map((iss) => {
15970
16106
  var _a2;
15971
16107
  (_a2 = iss).path ?? (_a2.path = []);
15972
- iss.path.unshift(path3);
16108
+ iss.path.unshift(path4);
15973
16109
  return iss;
15974
16110
  });
15975
16111
  }
@@ -16137,7 +16273,7 @@ function treeifyError2(error48, _mapper) {
16137
16273
  return issue3.message;
16138
16274
  };
16139
16275
  const result = { errors: [] };
16140
- const processError = (error49, path3 = []) => {
16276
+ const processError = (error49, path4 = []) => {
16141
16277
  var _a2, _b;
16142
16278
  for (const issue3 of error49.issues) {
16143
16279
  if (issue3.code === "invalid_union" && issue3.errors.length) {
@@ -16147,7 +16283,7 @@ function treeifyError2(error48, _mapper) {
16147
16283
  } else if (issue3.code === "invalid_element") {
16148
16284
  processError({ issues: issue3.issues }, issue3.path);
16149
16285
  } else {
16150
- const fullpath = [...path3, ...issue3.path];
16286
+ const fullpath = [...path4, ...issue3.path];
16151
16287
  if (fullpath.length === 0) {
16152
16288
  result.errors.push(mapper(issue3));
16153
16289
  continue;
@@ -16179,8 +16315,8 @@ function treeifyError2(error48, _mapper) {
16179
16315
  }
16180
16316
  function toDotPath2(_path) {
16181
16317
  const segs = [];
16182
- const path3 = _path.map((seg) => typeof seg === "object" ? seg.key : seg);
16183
- for (const seg of path3) {
16318
+ const path4 = _path.map((seg) => typeof seg === "object" ? seg.key : seg);
16319
+ for (const seg of path4) {
16184
16320
  if (typeof seg === "number")
16185
16321
  segs.push(`[${seg}]`);
16186
16322
  else if (typeof seg === "symbol")
@@ -27229,7 +27365,8 @@ function resolveCategory(categoryName, configCategories) {
27229
27365
 
27230
27366
  // src/tools/spawn-agent.ts
27231
27367
  function showToast(ctx, title, message, variant = "info") {
27232
- ctx.client?.tui?.showToast?.({
27368
+ const client = ctx.client;
27369
+ client.tui?.showToast?.({
27233
27370
  body: { title, message, variant, duration: 2000 }
27234
27371
  })?.catch?.(() => {});
27235
27372
  }
@@ -27334,6 +27471,18 @@ spawn_agent({
27334
27471
  if (!agents || agents.length === 0) {
27335
27472
  return "No agents specified.";
27336
27473
  }
27474
+ for (let i = 0;i < agents.length; i++) {
27475
+ const t = agents[i];
27476
+ if (!t.agent || typeof t.agent !== "string" || t.agent.trim().length === 0) {
27477
+ return `Error: agents[${i}].agent is required (non-empty string)`;
27478
+ }
27479
+ if (!t.prompt || typeof t.prompt !== "string" || t.prompt.trim().length === 0) {
27480
+ return `Error: agents[${i}].prompt is required (non-empty string)`;
27481
+ }
27482
+ if (!t.description || typeof t.description !== "string") {
27483
+ return `Error: agents[${i}].description is required`;
27484
+ }
27485
+ }
27337
27486
  const agentNames = agents.map((a) => a.agent);
27338
27487
  log("spawn_agent", { count: agents.length, agents: agentNames });
27339
27488
  showToast(ctx, "spawn_agent", `${agents.length} agents: ${agentNames.join(", ")}`);
@@ -27519,6 +27668,243 @@ Original task:
27519
27668
  ${original}`;
27520
27669
  }
27521
27670
 
27671
+ // src/tools/batch-read.ts
27672
+ import * as fs4 from "fs";
27673
+ import * as path4 from "path";
27674
+ var MAX_PATHS = 20;
27675
+ function resolvePath(projectDir, inputPath) {
27676
+ return path4.isAbsolute(inputPath) ? inputPath : path4.join(projectDir, inputPath);
27677
+ }
27678
+ function isProbablyBinary(buf) {
27679
+ const sample = buf.subarray(0, Math.min(buf.length, 8192));
27680
+ for (const b of sample) {
27681
+ if (b === 0)
27682
+ return true;
27683
+ }
27684
+ return false;
27685
+ }
27686
+ async function readOne(projectDir, inputPath) {
27687
+ const abs = resolvePath(projectDir, inputPath);
27688
+ try {
27689
+ const stat = await fs4.promises.stat(abs);
27690
+ if (!stat.isFile()) {
27691
+ return `=== ${inputPath} ===
27692
+ [File not found]
27693
+ `;
27694
+ }
27695
+ const buf = await fs4.promises.readFile(abs);
27696
+ if (isProbablyBinary(buf)) {
27697
+ return `=== ${inputPath} ===
27698
+ [Binary file]
27699
+ `;
27700
+ }
27701
+ const content = buf.toString("utf-8");
27702
+ return `=== ${inputPath} ===
27703
+ ${content}
27704
+ `;
27705
+ } catch (err) {
27706
+ if (err?.code === "ENOENT") {
27707
+ return `=== ${inputPath} ===
27708
+ [File not found]
27709
+ `;
27710
+ }
27711
+ log("batch_read failed to read file", { path: abs, error: String(err) });
27712
+ return `=== ${inputPath} ===
27713
+ [File not found]
27714
+ `;
27715
+ }
27716
+ }
27717
+ function createBatchReadTool(ctx) {
27718
+ return tool({
27719
+ description: "Read multiple files in parallel and concatenate contents.",
27720
+ args: {
27721
+ paths: tool.schema.array(tool.schema.string()).describe("File paths (max 20).")
27722
+ },
27723
+ execute: async ({ paths }) => {
27724
+ if (!Array.isArray(paths)) {
27725
+ return "Error: paths must be an array of strings";
27726
+ }
27727
+ if (paths.length > MAX_PATHS) {
27728
+ return `Error: maximum ${MAX_PATHS} paths allowed`;
27729
+ }
27730
+ const results = await Promise.all(paths.map((p) => readOne(ctx.directory, p)));
27731
+ return results.join("");
27732
+ }
27733
+ });
27734
+ }
27735
+
27736
+ // src/tools/continuity-ledger.ts
27737
+ import * as fs5 from "fs";
27738
+ import * as path5 from "path";
27739
+ function normalizeLedgerName(name) {
27740
+ const trimmed = (name ?? "").trim().replace(/\.md$/i, "");
27741
+ const spaced = trimmed.replace(/\s+/g, "-");
27742
+ const cleaned = spaced.replace(/[^A-Za-z0-9._-]/g, "");
27743
+ return cleaned.length > 0 ? cleaned : "ledger";
27744
+ }
27745
+ function ledgerDir(projectDir) {
27746
+ return path5.join(projectDir, ".opencode", "ledgers");
27747
+ }
27748
+ function ledgerPath(projectDir, name) {
27749
+ return path5.join(ledgerDir(projectDir), `${normalizeLedgerName(name)}.md`);
27750
+ }
27751
+ function createLedgerSaveTool(ctx) {
27752
+ return tool({
27753
+ description: "Save a continuity ledger markdown document to .opencode/ledgers/.",
27754
+ args: {
27755
+ name: tool.schema.string().describe('Ledger name (e.g. "auth-refactor").'),
27756
+ content: tool.schema.string().describe("Markdown content.")
27757
+ },
27758
+ execute: async ({ name, content }) => {
27759
+ const dir = ledgerDir(ctx.directory);
27760
+ const outPath = ledgerPath(ctx.directory, name);
27761
+ try {
27762
+ await fs5.promises.mkdir(dir, { recursive: true });
27763
+ await fs5.promises.writeFile(outPath, content, "utf-8");
27764
+ return `Saved to .opencode/ledgers/${path5.basename(outPath)}`;
27765
+ } catch (err) {
27766
+ log("ledger_save failed", { error: String(err) });
27767
+ return `Error: failed to save ledger`;
27768
+ }
27769
+ }
27770
+ });
27771
+ }
27772
+ function createLedgerLoadTool(ctx) {
27773
+ return tool({
27774
+ description: "Load a continuity ledger markdown document from .opencode/ledgers/.",
27775
+ args: {
27776
+ name: tool.schema.string().optional().describe("Ledger name (defaults to most recent).")
27777
+ },
27778
+ execute: async ({ name }) => {
27779
+ const dir = ledgerDir(ctx.directory);
27780
+ try {
27781
+ if (name && name.trim().length > 0) {
27782
+ const p = ledgerPath(ctx.directory, name);
27783
+ if (!fs5.existsSync(p))
27784
+ return "No ledgers found";
27785
+ return await fs5.promises.readFile(p, "utf-8");
27786
+ }
27787
+ if (!fs5.existsSync(dir))
27788
+ return "No ledgers found";
27789
+ const entries = await fs5.promises.readdir(dir);
27790
+ const files = entries.filter((e) => e.toLowerCase().endsWith(".md")).map((e) => path5.join(dir, e));
27791
+ if (files.length === 0)
27792
+ return "No ledgers found";
27793
+ let latest = files[0];
27794
+ let latestMtime = -1;
27795
+ for (const f of files) {
27796
+ try {
27797
+ const st = await fs5.promises.stat(f);
27798
+ if (st.mtimeMs > latestMtime) {
27799
+ latestMtime = st.mtimeMs;
27800
+ latest = f;
27801
+ }
27802
+ } catch {}
27803
+ }
27804
+ return await fs5.promises.readFile(latest, "utf-8");
27805
+ } catch (err) {
27806
+ log("ledger_load failed", { error: String(err) });
27807
+ return "No ledgers found";
27808
+ }
27809
+ }
27810
+ });
27811
+ }
27812
+
27813
+ // src/tools/ast-search.ts
27814
+ import * as fs6 from "fs";
27815
+ import * as path6 from "path";
27816
+ import { spawnSync } from "child_process";
27817
+ var MAX_MATCHES = 50;
27818
+ function stripAnsi(s) {
27819
+ return s.replace(/\u001b\[[0-9;]*m/g, "");
27820
+ }
27821
+ function resolveSearchPath(projectDir, p) {
27822
+ return path6.isAbsolute(p) ? p : path6.join(projectDir, p);
27823
+ }
27824
+ function candidatesFor(bin) {
27825
+ const out = [bin];
27826
+ if (process.platform === "win32") {
27827
+ out.push(`${bin}.exe`, `${bin}.cmd`, `${bin}.bat`);
27828
+ }
27829
+ return out;
27830
+ }
27831
+ function findAstGrepBinary() {
27832
+ const pathEnv = process.env.PATH ?? "";
27833
+ const dirs = pathEnv.split(path6.delimiter).filter(Boolean);
27834
+ const bins = ["sg", "ast-grep"];
27835
+ for (const dir of dirs) {
27836
+ for (const b of bins) {
27837
+ for (const cand of candidatesFor(b)) {
27838
+ const full = path6.join(dir, cand);
27839
+ try {
27840
+ const st = fs6.statSync(full);
27841
+ if (!st.isFile())
27842
+ continue;
27843
+ fs6.accessSync(full, fs6.constants.X_OK);
27844
+ return full;
27845
+ } catch {}
27846
+ }
27847
+ }
27848
+ }
27849
+ return null;
27850
+ }
27851
+ function normalizeOutput(stdout) {
27852
+ const lines = stripAnsi(stdout).split(`
27853
+ `).map((l) => l.trimEnd()).filter((l) => l.length > 0);
27854
+ const out = [];
27855
+ for (const line of lines) {
27856
+ const m3 = line.match(/^(.*?):(\d+):(\d+):(.*)$/);
27857
+ if (m3) {
27858
+ out.push(`${m3[1]}:${m3[2]}: ${m3[4].trim()}`);
27859
+ continue;
27860
+ }
27861
+ const m2 = line.match(/^(.*?):(\d+):(.*)$/);
27862
+ if (m2) {
27863
+ out.push(`${m2[1]}:${m2[2]}: ${m2[3].trim()}`);
27864
+ continue;
27865
+ }
27866
+ out.push(line);
27867
+ }
27868
+ return out.slice(0, MAX_MATCHES).join(`
27869
+ `);
27870
+ }
27871
+ function createAstSearchTool(ctx, binaryPath) {
27872
+ return tool({
27873
+ description: "AST-aware code search using ast-grep (sg).",
27874
+ args: {
27875
+ pattern: tool.schema.string().describe('ast-grep pattern (e.g. "console.log($$$)").'),
27876
+ path: tool.schema.string().optional().describe("Directory to search (default: project root)."),
27877
+ lang: tool.schema.string().optional().describe('Language (e.g. "typescript", "python").')
27878
+ },
27879
+ execute: async ({ pattern, path: searchPath, lang }) => {
27880
+ const target = resolveSearchPath(ctx.directory, searchPath ?? ".");
27881
+ const args = ["--pattern", pattern, target];
27882
+ if (lang && lang.trim().length > 0) {
27883
+ args.push("--lang", lang);
27884
+ }
27885
+ const res = spawnSync(binaryPath, args, {
27886
+ cwd: ctx.directory,
27887
+ encoding: "utf-8"
27888
+ });
27889
+ if (res.error) {
27890
+ log("ast_search spawn failed", { error: String(res.error) });
27891
+ return `Error: ${String(res.error)}`;
27892
+ }
27893
+ const stdout = res.stdout ?? "";
27894
+ const stderr = res.stderr ?? "";
27895
+ if ((res.status ?? 0) === 1 && stdout.trim().length === 0) {
27896
+ return "No matches";
27897
+ }
27898
+ if ((res.status ?? 0) !== 0 && (res.status ?? 0) !== 1) {
27899
+ log("ast_search failed", { status: res.status, stderr });
27900
+ return stderr.trim().length > 0 ? stderr.trim() : "Error: ast_search failed";
27901
+ }
27902
+ const normalized = normalizeOutput(stdout);
27903
+ return normalized.length > 0 ? normalized : "No matches";
27904
+ }
27905
+ });
27906
+ }
27907
+
27522
27908
  // src/hooks/todo-enforcer.ts
27523
27909
  var DEFAULT_MAX_ENFORCEMENTS = 5;
27524
27910
  var sessionState = new Map;
@@ -27601,7 +27987,7 @@ ${unfinished.map((t) => `- [ ] ${t.text}`).join(`
27601
27987
  }
27602
27988
 
27603
27989
  // src/hooks/comment-checker.ts
27604
- import * as fs3 from "fs";
27990
+ import * as fs7 from "fs";
27605
27991
  var AI_SLOP_PATTERNS = [
27606
27992
  /\/\/ .{80,}/,
27607
27993
  /\/\/ (This|The|We|Here|Note:)/i,
@@ -27680,7 +28066,7 @@ function createCommentCheckerHook(internalSessions, maxRatio = 0.3, slopThreshol
27680
28066
  if (!filePath || !isCodeFile(filePath))
27681
28067
  return;
27682
28068
  try {
27683
- const content = fs3.readFileSync(filePath, "utf-8");
28069
+ const content = fs7.readFileSync(filePath, "utf-8");
27684
28070
  const result = checkComments(content, filePath, maxRatio, slopThreshold);
27685
28071
  if (result.shouldWarn) {
27686
28072
  output.output += `
@@ -27852,14 +28238,16 @@ class Semaphore {
27852
28238
  this.active++;
27853
28239
  return;
27854
28240
  }
27855
- return new Promise((resolve) => {
28241
+ return new Promise((resolve2) => {
27856
28242
  this.queue.push(() => {
27857
28243
  this.active++;
27858
- resolve();
28244
+ resolve2();
27859
28245
  });
27860
28246
  });
27861
28247
  }
27862
28248
  release() {
28249
+ if (this.active <= 0)
28250
+ return;
27863
28251
  this.active--;
27864
28252
  const next = this.queue.shift();
27865
28253
  if (next)
@@ -27949,7 +28337,8 @@ async function registerMcps(ctx, disabled, apiKeys) {
27949
28337
  if (config3.env) {
27950
28338
  body.env = config3.env;
27951
28339
  }
27952
- await ctx.client.mcp?.add?.({ body });
28340
+ const client = ctx.client;
28341
+ await client.mcp?.add?.({ body });
27953
28342
  log(`MCP ${name} registered${apiKey ? " (with API key)" : ""}`);
27954
28343
  } catch (err) {
27955
28344
  log(`MCP ${name} registration failed: ${err}`);
@@ -27975,11 +28364,18 @@ var OpenCodeUltra = async (ctx) => {
27975
28364
  resolveAgentModel
27976
28365
  });
27977
28366
  const ralphTools = createRalphLoopTools(ctx, internalSessions);
28367
+ const batchRead = createBatchReadTool(ctx);
28368
+ const ledgerSave = createLedgerSaveTool(ctx);
28369
+ const ledgerLoad = createLedgerLoadTool(ctx);
28370
+ const astGrepBin = findAstGrepBinary();
28371
+ const astSearch = astGrepBin ? createAstSearchTool(ctx, astGrepBin) : null;
27978
28372
  const todoEnforcer = createTodoEnforcer(ctx, internalSessions, pluginConfig.todo_enforcer?.maxEnforcements);
27979
28373
  const commentCheckerHook = createCommentCheckerHook(internalSessions, pluginConfig.comment_checker?.maxRatio, pluginConfig.comment_checker?.slopThreshold);
27980
28374
  const tokenTruncationHook = createTokenTruncationHook(internalSessions, pluginConfig.token_truncation?.maxChars);
27981
- const sessionCompactionHook = createSessionCompactionHook(ctx, internalSessions);
27982
- const pendingKeywords = new Map;
28375
+ const sessionCompactionHook = createSessionCompactionHook({ client: ctx.client }, internalSessions);
28376
+ const fragmentInjector = createFragmentInjector(ctx, internalSessions, pluginConfig.fragments);
28377
+ const promptRendererHook = createPromptRendererHook(internalSessions, pluginConfig.prompt_renderer);
28378
+ const pendingKeywords = new TtlMap({ maxSize: 200, ttlMs: 5 * 60 * 1000 });
27983
28379
  log("Config loaded", {
27984
28380
  agentCount: Object.keys(agents).length,
27985
28381
  disabledHooks: [...disabledHooks],
@@ -27999,6 +28395,18 @@ var OpenCodeUltra = async (ctx) => {
27999
28395
  if (!disabledTools.has("cancel_ralph")) {
28000
28396
  toolRegistry.cancel_ralph = ralphTools.cancel_ralph;
28001
28397
  }
28398
+ if (!disabledTools.has("batch_read")) {
28399
+ toolRegistry.batch_read = batchRead;
28400
+ }
28401
+ if (!disabledTools.has("ledger_save")) {
28402
+ toolRegistry.ledger_save = ledgerSave;
28403
+ }
28404
+ if (!disabledTools.has("ledger_load")) {
28405
+ toolRegistry.ledger_load = ledgerLoad;
28406
+ }
28407
+ if (!disabledTools.has("ast_search") && astSearch) {
28408
+ toolRegistry.ast_search = astSearch;
28409
+ }
28002
28410
  return {
28003
28411
  tool: toolRegistry,
28004
28412
  config: async (config3) => {
@@ -28048,7 +28456,8 @@ var OpenCodeUltra = async (ctx) => {
28048
28456
  if (output.message.variant === undefined) {
28049
28457
  output.message.variant = "max";
28050
28458
  }
28051
- ctx.client?.tui?.showToast?.({
28459
+ const client = ctx.client;
28460
+ client.tui?.showToast?.({
28052
28461
  body: {
28053
28462
  title: "ULTRAWORK MODE",
28054
28463
  message: "Maximum precision engaged. All agents at your disposal.",
@@ -28107,6 +28516,12 @@ ${arch}`);
28107
28516
  ${style}`);
28108
28517
  }
28109
28518
  }
28519
+ if (!disabledHooks.has("fragment-injector")) {
28520
+ await fragmentInjector(input, output);
28521
+ }
28522
+ if (!disabledHooks.has("prompt-renderer")) {
28523
+ promptRendererHook(input, output);
28524
+ }
28110
28525
  },
28111
28526
  "experimental.session.compacting": async (input, output) => {
28112
28527
  if (disabledHooks.has("session-compaction"))
@@ -1,3 +1,5 @@
1
1
  export { getConfigDir, getCacheDir, getDataDir } from "./paths";
2
2
  export { parseJsonc } from "./jsonc";
3
3
  export { log } from "./log";
4
+ export type { ExtendedClient, ModelRef, ToastOptions, SystemTransformInput } from "./types";
5
+ export { TtlMap } from "./ttl-map";
@@ -0,0 +1,19 @@
1
+ /**
2
+ * Simple Map with max size eviction (LRU-like: evicts oldest entry when full).
3
+ * Optionally supports TTL-based expiry.
4
+ */
5
+ export declare class TtlMap<K, V> {
6
+ private map;
7
+ private maxSize;
8
+ private ttlMs;
9
+ constructor(opts?: {
10
+ maxSize?: number;
11
+ ttlMs?: number;
12
+ });
13
+ get(key: K): V | undefined;
14
+ set(key: K, value: V): void;
15
+ has(key: K): boolean;
16
+ delete(key: K): boolean;
17
+ get size(): number;
18
+ clear(): void;
19
+ }