opencode-ultra 0.2.1 → 0.3.0

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
@@ -38,6 +38,10 @@ declare const PluginConfigSchema: z.ZodObject<{
38
38
  disabled_hooks: z.ZodOptional<z.ZodArray<z.ZodString>>;
39
39
  disabled_tools: z.ZodOptional<z.ZodArray<z.ZodString>>;
40
40
  disabled_mcps: z.ZodOptional<z.ZodArray<z.ZodString>>;
41
+ token_truncation: z.ZodOptional<z.ZodObject<{
42
+ maxChars: z.ZodOptional<z.ZodNumber>;
43
+ }, z.core.$strip>>;
44
+ demote_builtin: z.ZodOptional<z.ZodBoolean>;
41
45
  background_task: z.ZodOptional<z.ZodObject<{
42
46
  defaultConcurrency: z.ZodOptional<z.ZodNumber>;
43
47
  providerConcurrency: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodNumber>>;
@@ -7,7 +7,7 @@ export interface CommentCheckResult {
7
7
  export declare function isCodeFile(filePath: string): boolean;
8
8
  export declare function checkComments(content: string, filePath: string, maxRatio?: number, slopThreshold?: number): CommentCheckResult;
9
9
  export declare function createCommentCheckerHook(internalSessions: Set<string>, maxRatio?: number, slopThreshold?: number): (input: {
10
- tool: {
10
+ tool: string | {
11
11
  name: string;
12
12
  };
13
13
  args: Record<string, unknown>;
@@ -1,5 +1,5 @@
1
1
  export interface DetectedKeyword {
2
- type: "ultrawork" | "search" | "analyze";
2
+ type: "ultrawork" | "search" | "analyze" | "think";
3
3
  message: string;
4
4
  }
5
5
  export declare function removeCodeBlocks(text: string): string;
@@ -1,2 +1,4 @@
1
1
  export declare function loadRules(projectDir: string): string | null;
2
+ export declare function loadArchitecture(projectDir: string): string | null;
3
+ export declare function loadCodeStyle(projectDir: string): string | null;
2
4
  export declare function clearRulesCache(): void;
@@ -0,0 +1,21 @@
1
+ type MessageLike = {
2
+ role?: string;
3
+ content?: string;
4
+ info?: {
5
+ role?: string;
6
+ };
7
+ parts?: Array<{
8
+ type: string;
9
+ text?: string;
10
+ }>;
11
+ };
12
+ export declare function buildSessionSummary(messages: MessageLike[]): string;
13
+ export declare function createSessionCompactionHook(ctx: {
14
+ client: any;
15
+ }, internalSessions: Set<string>): (input: {
16
+ sessionID: string;
17
+ }, output: {
18
+ context: string[];
19
+ prompt?: string;
20
+ }) => Promise<void>;
21
+ export {};
@@ -0,0 +1,9 @@
1
+ export declare function truncateMiddle(input: string, maxChars: number): string;
2
+ export declare function createTokenTruncationHook(internalSessions: Set<string>, maxChars?: number): (input: {
3
+ tool: string | {
4
+ name: string;
5
+ };
6
+ sessionID: string;
7
+ }, output: {
8
+ output: string;
9
+ }) => void;
package/dist/index.js CHANGED
@@ -14400,6 +14400,10 @@ var PluginConfigSchema = exports_external.object({
14400
14400
  disabled_hooks: exports_external.array(exports_external.string()).optional(),
14401
14401
  disabled_tools: exports_external.array(exports_external.string()).optional(),
14402
14402
  disabled_mcps: exports_external.array(exports_external.string()).optional(),
14403
+ token_truncation: exports_external.object({
14404
+ maxChars: exports_external.number().min(1).optional()
14405
+ }).optional(),
14406
+ demote_builtin: exports_external.boolean().optional(),
14403
14407
  background_task: exports_external.object({
14404
14408
  defaultConcurrency: exports_external.number().optional(),
14405
14409
  providerConcurrency: exports_external.record(exports_external.string(), exports_external.number()).optional(),
@@ -14510,15 +14514,15 @@ function loadConfig(projectDir) {
14510
14514
  // src/agents/index.ts
14511
14515
  var BUILTIN_AGENTS = {
14512
14516
  sisyphus: {
14513
- model: "anthropic/claude-opus-4-5",
14514
- description: "Primary orchestrator \u2014 analyzes requests, delegates to specialists, verifies results",
14517
+ model: "openai-codex/gpt-5.3-codex",
14518
+ description: "Primary orchestrator \u2014 analyzes requests, reads code directly, delegates implementation to specialists",
14515
14519
  prompt: "__DYNAMIC__",
14516
14520
  maxTokens: 64000,
14517
- thinking: { type: "enabled", budgetTokens: 32000 },
14521
+ reasoningEffort: "high",
14518
14522
  tools: {
14519
- Grep: false,
14520
- Glob: false,
14521
- Read: false,
14523
+ Grep: true,
14524
+ Glob: true,
14525
+ Read: true,
14522
14526
  Write: false,
14523
14527
  Edit: false,
14524
14528
  Bash: false
@@ -14622,17 +14626,19 @@ ${example1}
14622
14626
 
14623
14627
  Estimated agents: N | Phases: M`;
14624
14628
  }
14625
- var SISYPHUS_PROMPT_TEMPLATE = (agentTable, planExample) => `You are Sisyphus, the ORCHESTRATOR. You do NOT do the work yourself.
14629
+ var SISYPHUS_PROMPT_TEMPLATE = (agentTable, planExample) => `You are Sisyphus, the ORCHESTRATOR. You read code yourself and delegate implementation.
14626
14630
 
14627
14631
  ## YOUR ROLE
14628
- You are a conductor. You THINK, PLAN, and DELEGATE via spawn_agent.
14629
- Your specialist agents run on models optimized for their tasks \u2014 faster and cheaper than you.
14630
- When you do Grep/Glob/Read directly, you waste your own expensive tokens on work a specialist does better.
14632
+ You are a conductor who can read the score directly. You THINK, READ, PLAN, and DELEGATE writing via spawn_agent.
14633
+ You can use Grep, Glob, Read to understand the codebase \u2014 this is FASTER than spawning an explore agent.
14634
+ Your specialist agents handle WRITING code, running tests, and implementation tasks.
14631
14635
 
14632
14636
  ## RULES
14633
- - Do NOT use Grep, Glob, Read, Write, Edit, Bash directly. Delegate via spawn_agent.
14637
+ - You CAN use Grep, Glob, Read directly \u2014 read code to understand context before planning.
14638
+ - You CANNOT use Write, Edit, Bash \u2014 delegate these via spawn_agent.
14634
14639
  - You may: use spawn_agent, track TODOs, ask the user questions, synthesize agent results.
14635
- - Always run independent tasks in PARALLEL via spawn_agent.
14640
+ - Always run independent IMPLEMENTATION tasks in PARALLEL via spawn_agent.
14641
+ - Do NOT spawn agents just to read files \u2014 do that yourself.
14636
14642
 
14637
14643
  ## AGENTS (via spawn_agent)
14638
14644
  ${agentTable}
@@ -14706,6 +14712,7 @@ function buildAgents(config2) {
14706
14712
  var CODE_BLOCK_PATTERN = /```[\s\S]*?```/g;
14707
14713
  var INLINE_CODE_PATTERN = /`[^`]+`/g;
14708
14714
  var ULTRAWORK_PATTERN = /\b(ultrawork|ulw)\b/i;
14715
+ 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;
14709
14716
  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;
14710
14717
  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;
14711
14718
  function removeCodeBlocks(text) {
@@ -14717,7 +14724,8 @@ function extractPromptText(parts) {
14717
14724
  var KEYWORD_DEFS = [
14718
14725
  { pattern: ULTRAWORK_PATTERN, type: "ultrawork", getMessage: () => ULTRAWORK_MESSAGE },
14719
14726
  { pattern: SEARCH_PATTERN, type: "search", getMessage: () => SEARCH_MESSAGE },
14720
- { pattern: ANALYZE_PATTERN, type: "analyze", getMessage: () => ANALYZE_MESSAGE }
14727
+ { pattern: ANALYZE_PATTERN, type: "analyze", getMessage: () => ANALYZE_MESSAGE },
14728
+ { pattern: THINK_PATTERN, type: "think", getMessage: () => THINK_MESSAGE }
14721
14729
  ];
14722
14730
  function detectKeywords(text) {
14723
14731
  const clean = removeCodeBlocks(text);
@@ -14806,32 +14814,65 @@ IF COMPLEX \u2014 DO NOT STRUGGLE ALONE. Consult specialists:
14806
14814
  - **Oracle**: Conventional problems (architecture, debugging, complex logic)
14807
14815
 
14808
14816
  SYNTHESIZE findings before proceeding.`;
14817
+ var THINK_MESSAGE = `Extended thinking enabled. Take your time to reason thoroughly.`;
14809
14818
 
14810
14819
  // src/hooks/rules-injector.ts
14811
14820
  import * as fs2 from "fs";
14812
14821
  import * as path2 from "path";
14813
- var cachedRules = null;
14814
- var cachedPath = null;
14815
- function loadRules(projectDir) {
14816
- const rulesPath = path2.join(projectDir, ".opencode", "rules.md");
14822
+ var cache = new Map;
14823
+ function loadCachedFile(filePath) {
14817
14824
  try {
14818
- if (!fs2.existsSync(rulesPath))
14825
+ if (!fs2.existsSync(filePath))
14819
14826
  return null;
14820
- const stat = fs2.statSync(rulesPath);
14827
+ const stat = fs2.statSync(filePath);
14821
14828
  const mtime = stat.mtimeMs;
14822
- if (cachedPath === rulesPath && cachedRules && cachedRules.mtime === mtime) {
14823
- return cachedRules.content;
14824
- }
14825
- const content = fs2.readFileSync(rulesPath, "utf-8");
14826
- cachedPath = rulesPath;
14827
- cachedRules = { content, mtime };
14828
- log(`Rules loaded from ${rulesPath}`);
14829
+ const cached2 = cache.get(filePath);
14830
+ if (cached2 && cached2.mtime === mtime)
14831
+ return cached2.content;
14832
+ const content = fs2.readFileSync(filePath, "utf-8");
14833
+ cache.set(filePath, { content, mtime });
14829
14834
  return content;
14830
14835
  } catch (err) {
14831
- log(`Error loading rules: ${err}`);
14836
+ log(`Error loading file: ${err}`, { filePath });
14832
14837
  return null;
14833
14838
  }
14834
14839
  }
14840
+ function loadFirstExisting(projectDir, relativePaths) {
14841
+ for (const rel of relativePaths) {
14842
+ const p = path2.join(projectDir, rel);
14843
+ const content = loadCachedFile(p);
14844
+ if (content !== null)
14845
+ return { path: p, content };
14846
+ }
14847
+ return null;
14848
+ }
14849
+ function loadRules(projectDir) {
14850
+ const hit = loadFirstExisting(projectDir, [path2.join(".opencode", "rules.md")]);
14851
+ if (!hit)
14852
+ return null;
14853
+ log(`Rules loaded from ${hit.path}`);
14854
+ return hit.content;
14855
+ }
14856
+ function loadArchitecture(projectDir) {
14857
+ const hit = loadFirstExisting(projectDir, [
14858
+ path2.join(".opencode", "ARCHITECTURE.md"),
14859
+ "ARCHITECTURE.md"
14860
+ ]);
14861
+ if (!hit)
14862
+ return null;
14863
+ log(`Architecture loaded from ${hit.path}`);
14864
+ return hit.content;
14865
+ }
14866
+ function loadCodeStyle(projectDir) {
14867
+ const hit = loadFirstExisting(projectDir, [
14868
+ path2.join(".opencode", "CODE_STYLE.md"),
14869
+ "CODE_STYLE.md"
14870
+ ]);
14871
+ if (!hit)
14872
+ return null;
14873
+ log(`Code style loaded from ${hit.path}`);
14874
+ return hit.content;
14875
+ }
14835
14876
 
14836
14877
  // node_modules/@opencode-ai/plugin/node_modules/zod/v4/classic/external.js
14837
14878
  var exports_external2 = {};
@@ -27630,7 +27671,8 @@ function buildWarning(ratio, slopCount, maxRatio, slopThreshold) {
27630
27671
  }
27631
27672
  function createCommentCheckerHook(internalSessions, maxRatio = 0.3, slopThreshold = 3) {
27632
27673
  return (input, output) => {
27633
- if (input.tool.name !== "Write" && input.tool.name !== "Edit")
27674
+ const toolName = typeof input.tool === "string" ? input.tool : input.tool.name;
27675
+ if (toolName !== "Write" && toolName !== "Edit")
27634
27676
  return;
27635
27677
  if (internalSessions.has(input.sessionID))
27636
27678
  return;
@@ -27653,6 +27695,150 @@ function createCommentCheckerHook(internalSessions, maxRatio = 0.3, slopThreshol
27653
27695
  };
27654
27696
  }
27655
27697
 
27698
+ // src/hooks/token-truncation.ts
27699
+ var DEFAULT_MAX_CHARS = 30000;
27700
+ function truncateMiddle(input, maxChars) {
27701
+ if (!Number.isFinite(maxChars) || maxChars <= 0)
27702
+ return input;
27703
+ if (input.length <= maxChars)
27704
+ return input;
27705
+ if (maxChars < 32)
27706
+ return input.slice(0, maxChars);
27707
+ const len = input.length;
27708
+ let head = Math.floor(maxChars * 0.4);
27709
+ let tail = Math.floor(maxChars * 0.4);
27710
+ head = Math.min(Math.max(head, 1), maxChars);
27711
+ const buildMarker = (removed2) => `
27712
+
27713
+ ... [truncated ${removed2} chars] ...
27714
+
27715
+ `;
27716
+ const finalize2 = (h, t) => {
27717
+ const removed2 = Math.max(0, len - h - t);
27718
+ const marker2 = buildMarker(removed2);
27719
+ const availableTail2 = maxChars - h - marker2.length;
27720
+ const finalTail = Math.max(0, Math.min(t, availableTail2));
27721
+ const finalRemoved = Math.max(0, len - h - finalTail);
27722
+ const finalMarker = buildMarker(finalRemoved);
27723
+ const availableTail22 = maxChars - h - finalMarker.length;
27724
+ const finalTail2 = Math.max(0, Math.min(finalTail, availableTail22));
27725
+ const removed22 = Math.max(0, len - h - finalTail2);
27726
+ const marker22 = buildMarker(removed22);
27727
+ const budget = h + marker22.length + finalTail2;
27728
+ if (budget > maxChars) {
27729
+ return input.slice(0, maxChars);
27730
+ }
27731
+ return input.slice(0, h) + marker22 + input.slice(len - finalTail2);
27732
+ };
27733
+ const removed = Math.max(0, len - head - tail);
27734
+ const marker = buildMarker(removed);
27735
+ const availableTail = maxChars - head - marker.length;
27736
+ if (availableTail < 0) {
27737
+ const shrinkHead = Math.max(0, maxChars - marker.length);
27738
+ return finalize2(shrinkHead, 0);
27739
+ }
27740
+ tail = Math.min(tail, availableTail);
27741
+ return finalize2(head, tail);
27742
+ }
27743
+ function createTokenTruncationHook(internalSessions, maxChars = DEFAULT_MAX_CHARS) {
27744
+ const budget = Number.isFinite(maxChars) ? Math.max(1, Math.floor(maxChars)) : DEFAULT_MAX_CHARS;
27745
+ return (input, output) => {
27746
+ if (internalSessions.has(input.sessionID))
27747
+ return;
27748
+ const before = output.output;
27749
+ const after = truncateMiddle(before, budget);
27750
+ if (after !== before) {
27751
+ output.output = after;
27752
+ const toolName = typeof input.tool === "string" ? input.tool : input.tool.name;
27753
+ log("Token truncation applied", { tool: toolName, before: before.length, after: after.length });
27754
+ }
27755
+ };
27756
+ }
27757
+
27758
+ // src/hooks/session-compaction.ts
27759
+ function partsToText(parts) {
27760
+ if (!parts)
27761
+ return "";
27762
+ return parts.filter((p) => p && p.type === "text" && typeof p.text === "string").map((p) => p.text).join("");
27763
+ }
27764
+ function getRole(m) {
27765
+ return (m.role ?? m.info?.role ?? "").toLowerCase();
27766
+ }
27767
+ function getText(m) {
27768
+ if (typeof m.content === "string")
27769
+ return m.content;
27770
+ return partsToText(m.parts);
27771
+ }
27772
+ function uniq(items) {
27773
+ const seen = new Set;
27774
+ const out = [];
27775
+ for (const it of items) {
27776
+ const k = typeof it === "string" ? it : JSON.stringify(it);
27777
+ if (seen.has(k))
27778
+ continue;
27779
+ seen.add(k);
27780
+ out.push(it);
27781
+ }
27782
+ return out;
27783
+ }
27784
+ var FILE_PATH_RE = /(?:(?:[A-Za-z]:\\)?[\w.-]+(?:[\\/][\w.-]+)+)\.(?:ts|tsx|js|jsx|mjs|cjs|json|md|toml|yaml|yml|py|go|rs|java|kt|c|cpp|h|hpp|cs|swift|vue|svelte)/g;
27785
+ function buildSessionSummary(messages) {
27786
+ const assistantTexts = messages.filter((m) => getRole(m) === "assistant").map((m) => getText(m)).map((t) => t.trim()).filter(Boolean);
27787
+ const combined = assistantTexts.join(`
27788
+ `);
27789
+ const files = uniq((combined.match(FILE_PATH_RE) ?? []).map((s) => s.replace(/^[`"']|[`"']$/g, "")));
27790
+ const decisionLines = uniq(assistantTexts.flatMap((t) => t.split(`
27791
+ `)).map((l) => l.trim()).filter((l) => /(decid|we will|should|must|chosen|choice|\u65B9\u91DD|\u6C7A\u5B9A|\u63A1\u7528)/i.test(l)).slice(0, 10));
27792
+ const nextLines = uniq(assistantTexts.flatMap((t) => t.split(`
27793
+ `)).map((l) => l.trim()).filter((l) => /(next\s*steps?|todo|next:|\u6B21\s*\u306B|\u3084\u308B|TODO)/i.test(l)).slice(0, 10));
27794
+ const currentState = (assistantTexts[assistantTexts.length - 1] ?? "").slice(0, 800).trim();
27795
+ const lines = [];
27796
+ lines.push("## Session Summary");
27797
+ lines.push("");
27798
+ lines.push("### Key Decisions");
27799
+ if (decisionLines.length === 0)
27800
+ lines.push("- (none)");
27801
+ else
27802
+ for (const l of decisionLines)
27803
+ lines.push(`- ${l}`);
27804
+ lines.push("");
27805
+ lines.push("### Files Modified");
27806
+ if (files.length === 0)
27807
+ lines.push("- (none)");
27808
+ else
27809
+ for (const f of files)
27810
+ lines.push(`- ${f}`);
27811
+ lines.push("");
27812
+ lines.push("### Current State");
27813
+ lines.push(currentState ? `- ${currentState}` : "- (none)");
27814
+ lines.push("");
27815
+ lines.push("### Next Steps");
27816
+ if (nextLines.length === 0)
27817
+ lines.push("- (none)");
27818
+ else
27819
+ for (const l of nextLines)
27820
+ lines.push(`- ${l}`);
27821
+ lines.push("");
27822
+ return lines.join(`
27823
+ `);
27824
+ }
27825
+ function createSessionCompactionHook(ctx, internalSessions) {
27826
+ return async (input, output) => {
27827
+ if (internalSessions.has(input.sessionID))
27828
+ return;
27829
+ try {
27830
+ const res = await ctx.client?.session?.listMessages?.({ id: input.sessionID });
27831
+ const msgs = res?.messages ?? res ?? [];
27832
+ const summary = buildSessionSummary(msgs);
27833
+ output.context.push(summary);
27834
+ output.prompt = "Output a compact session summary in the EXACT format below. " + "Use the provided '## Session Summary' scaffold from context as your base; do not add extra sections.";
27835
+ } catch (err) {
27836
+ log("Session compaction hook failed", { error: err });
27837
+ output.prompt = "Summarize the session using this exact template: ## Session Summary; ### Key Decisions; ### Files Modified; ### Current State; ### Next Steps.";
27838
+ }
27839
+ };
27840
+ }
27841
+
27656
27842
  // src/concurrency/semaphore.ts
27657
27843
  class Semaphore {
27658
27844
  max;
@@ -27791,6 +27977,8 @@ var OpenCodeUltra = async (ctx) => {
27791
27977
  const ralphTools = createRalphLoopTools(ctx, internalSessions);
27792
27978
  const todoEnforcer = createTodoEnforcer(ctx, internalSessions, pluginConfig.todo_enforcer?.maxEnforcements);
27793
27979
  const commentCheckerHook = createCommentCheckerHook(internalSessions, pluginConfig.comment_checker?.maxRatio, pluginConfig.comment_checker?.slopThreshold);
27980
+ const tokenTruncationHook = createTokenTruncationHook(internalSessions, pluginConfig.token_truncation?.maxChars);
27981
+ const sessionCompactionHook = createSessionCompactionHook(ctx, internalSessions);
27794
27982
  const pendingKeywords = new Map;
27795
27983
  log("Config loaded", {
27796
27984
  agentCount: Object.keys(agents).length,
@@ -27835,6 +28023,15 @@ var OpenCodeUltra = async (ctx) => {
27835
28023
  ...existingAgents,
27836
28024
  ...agentConfigs
27837
28025
  };
28026
+ if (pluginConfig.demote_builtin !== false) {
28027
+ const agentMap = config3.agent;
28028
+ for (const name of ["build", "plan", "triage", "docs"]) {
28029
+ const cur = agentMap[name];
28030
+ if (cur && typeof cur === "object") {
28031
+ agentMap[name] = { ...cur, mode: "subagent" };
28032
+ }
28033
+ }
28034
+ }
27838
28035
  log("Agents registered", { agents: Object.keys(agentConfigs) });
27839
28036
  },
27840
28037
  "chat.message": async (input, output) => {
@@ -27896,11 +28093,33 @@ var OpenCodeUltra = async (ctx) => {
27896
28093
  ${rules}`);
27897
28094
  }
27898
28095
  }
28096
+ if (!disabledHooks.has("context-injector")) {
28097
+ const arch = loadArchitecture(ctx.directory);
28098
+ if (arch) {
28099
+ output.system.push(`## Architecture
28100
+
28101
+ ${arch}`);
28102
+ }
28103
+ const style = loadCodeStyle(ctx.directory);
28104
+ if (style) {
28105
+ output.system.push(`## Code Style
28106
+
28107
+ ${style}`);
28108
+ }
28109
+ }
28110
+ },
28111
+ "experimental.session.compacting": async (input, output) => {
28112
+ if (disabledHooks.has("session-compaction"))
28113
+ return;
28114
+ await sessionCompactionHook(input, output);
27899
28115
  },
27900
28116
  "tool.execute.after": async (input, output) => {
27901
28117
  if (!disabledHooks.has("comment-checker")) {
27902
28118
  commentCheckerHook(input, output);
27903
28119
  }
28120
+ if (!disabledHooks.has("token-truncation")) {
28121
+ tokenTruncationHook(input, output);
28122
+ }
27904
28123
  },
27905
28124
  event: async ({ event }) => {
27906
28125
  if (event.type === "session.deleted") {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "opencode-ultra",
3
- "version": "0.2.1",
3
+ "version": "0.3.0",
4
4
  "description": "Lightweight OpenCode 1.2.x plugin — ultrawork mode, multi-agent orchestration, rules injection",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",