opencode-ultra 0.2.2 → 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.
|
@@ -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,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
|
@@ -14819,28 +14819,60 @@ var THINK_MESSAGE = `Extended thinking enabled. Take your time to reason thoroug
|
|
|
14819
14819
|
// src/hooks/rules-injector.ts
|
|
14820
14820
|
import * as fs2 from "fs";
|
|
14821
14821
|
import * as path2 from "path";
|
|
14822
|
-
var
|
|
14823
|
-
|
|
14824
|
-
function loadRules(projectDir) {
|
|
14825
|
-
const rulesPath = path2.join(projectDir, ".opencode", "rules.md");
|
|
14822
|
+
var cache = new Map;
|
|
14823
|
+
function loadCachedFile(filePath) {
|
|
14826
14824
|
try {
|
|
14827
|
-
if (!fs2.existsSync(
|
|
14825
|
+
if (!fs2.existsSync(filePath))
|
|
14828
14826
|
return null;
|
|
14829
|
-
const stat = fs2.statSync(
|
|
14827
|
+
const stat = fs2.statSync(filePath);
|
|
14830
14828
|
const mtime = stat.mtimeMs;
|
|
14831
|
-
|
|
14832
|
-
|
|
14833
|
-
|
|
14834
|
-
const content = fs2.readFileSync(
|
|
14835
|
-
|
|
14836
|
-
cachedRules = { content, mtime };
|
|
14837
|
-
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 });
|
|
14838
14834
|
return content;
|
|
14839
14835
|
} catch (err) {
|
|
14840
|
-
log(`Error loading
|
|
14836
|
+
log(`Error loading file: ${err}`, { filePath });
|
|
14841
14837
|
return null;
|
|
14842
14838
|
}
|
|
14843
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
|
+
}
|
|
14844
14876
|
|
|
14845
14877
|
// node_modules/@opencode-ai/plugin/node_modules/zod/v4/classic/external.js
|
|
14846
14878
|
var exports_external2 = {};
|
|
@@ -27639,7 +27671,8 @@ function buildWarning(ratio, slopCount, maxRatio, slopThreshold) {
|
|
|
27639
27671
|
}
|
|
27640
27672
|
function createCommentCheckerHook(internalSessions, maxRatio = 0.3, slopThreshold = 3) {
|
|
27641
27673
|
return (input, output) => {
|
|
27642
|
-
|
|
27674
|
+
const toolName = typeof input.tool === "string" ? input.tool : input.tool.name;
|
|
27675
|
+
if (toolName !== "Write" && toolName !== "Edit")
|
|
27643
27676
|
return;
|
|
27644
27677
|
if (internalSessions.has(input.sessionID))
|
|
27645
27678
|
return;
|
|
@@ -27662,6 +27695,150 @@ function createCommentCheckerHook(internalSessions, maxRatio = 0.3, slopThreshol
|
|
|
27662
27695
|
};
|
|
27663
27696
|
}
|
|
27664
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
|
+
|
|
27665
27842
|
// src/concurrency/semaphore.ts
|
|
27666
27843
|
class Semaphore {
|
|
27667
27844
|
max;
|
|
@@ -27800,6 +27977,8 @@ var OpenCodeUltra = async (ctx) => {
|
|
|
27800
27977
|
const ralphTools = createRalphLoopTools(ctx, internalSessions);
|
|
27801
27978
|
const todoEnforcer = createTodoEnforcer(ctx, internalSessions, pluginConfig.todo_enforcer?.maxEnforcements);
|
|
27802
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);
|
|
27803
27982
|
const pendingKeywords = new Map;
|
|
27804
27983
|
log("Config loaded", {
|
|
27805
27984
|
agentCount: Object.keys(agents).length,
|
|
@@ -27844,6 +28023,15 @@ var OpenCodeUltra = async (ctx) => {
|
|
|
27844
28023
|
...existingAgents,
|
|
27845
28024
|
...agentConfigs
|
|
27846
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
|
+
}
|
|
27847
28035
|
log("Agents registered", { agents: Object.keys(agentConfigs) });
|
|
27848
28036
|
},
|
|
27849
28037
|
"chat.message": async (input, output) => {
|
|
@@ -27905,11 +28093,33 @@ var OpenCodeUltra = async (ctx) => {
|
|
|
27905
28093
|
${rules}`);
|
|
27906
28094
|
}
|
|
27907
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);
|
|
27908
28115
|
},
|
|
27909
28116
|
"tool.execute.after": async (input, output) => {
|
|
27910
28117
|
if (!disabledHooks.has("comment-checker")) {
|
|
27911
28118
|
commentCheckerHook(input, output);
|
|
27912
28119
|
}
|
|
28120
|
+
if (!disabledHooks.has("token-truncation")) {
|
|
28121
|
+
tokenTruncationHook(input, output);
|
|
28122
|
+
}
|
|
27913
28123
|
},
|
|
27914
28124
|
event: async ({ event }) => {
|
|
27915
28125
|
if (event.type === "session.deleted") {
|
package/package.json
CHANGED