opencode-ultra 0.2.2 → 0.4.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 +13 -0
- package/dist/hooks/comment-checker.d.ts +1 -1
- package/dist/hooks/fragment-injector.d.ts +9 -0
- package/dist/hooks/prompt-renderer.d.ts +13 -0
- package/dist/hooks/rules-injector.d.ts +2 -0
- package/dist/hooks/session-compaction.d.ts +21 -0
- package/dist/hooks/token-truncation.d.ts +9 -0
- package/dist/index.js +595 -30
- package/dist/tools/ast-search.d.ts +3 -0
- package/dist/tools/batch-read.d.ts +2 -0
- package/dist/tools/continuity-ledger.d.ts +4 -0
- package/package.json +1 -1
package/dist/config.d.ts
CHANGED
|
@@ -34,6 +34,19 @@ declare const PluginConfigSchema: z.ZodObject<{
|
|
|
34
34
|
model: z.ZodOptional<z.ZodString>;
|
|
35
35
|
variant: z.ZodOptional<z.ZodString>;
|
|
36
36
|
}, z.core.$loose>>>;
|
|
37
|
+
fragments: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodArray<z.ZodString>>>;
|
|
38
|
+
prompt_renderer: z.ZodOptional<z.ZodObject<{
|
|
39
|
+
default: z.ZodOptional<z.ZodEnum<{
|
|
40
|
+
markdown: "markdown";
|
|
41
|
+
xml: "xml";
|
|
42
|
+
json: "json";
|
|
43
|
+
}>>;
|
|
44
|
+
model_overrides: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodEnum<{
|
|
45
|
+
markdown: "markdown";
|
|
46
|
+
xml: "xml";
|
|
47
|
+
json: "json";
|
|
48
|
+
}>>>;
|
|
49
|
+
}, z.core.$strip>>;
|
|
37
50
|
disabled_agents: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
38
51
|
disabled_hooks: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
39
52
|
disabled_tools: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
@@ -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>;
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export type FragmentsConfig = Record<string, string[]>;
|
|
2
|
+
export declare function createFragmentInjector(ctx: {
|
|
3
|
+
directory: string;
|
|
4
|
+
}, internalSessions: Set<string>, fragments?: FragmentsConfig): (input: {
|
|
5
|
+
sessionID?: string;
|
|
6
|
+
agent?: string;
|
|
7
|
+
}, output: {
|
|
8
|
+
system: string[];
|
|
9
|
+
}) => Promise<void>;
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
export type PromptFormat = "markdown" | "xml" | "json";
|
|
2
|
+
export type PromptRendererConfig = {
|
|
3
|
+
default: PromptFormat;
|
|
4
|
+
model_overrides: Record<string, PromptFormat>;
|
|
5
|
+
};
|
|
6
|
+
export declare function resolvePromptFormat(inputModel: any, config?: Partial<PromptRendererConfig>): PromptFormat;
|
|
7
|
+
export declare function formatSystemSection(format: PromptFormat, content: string): string;
|
|
8
|
+
export declare function createPromptRendererHook(internalSessions: Set<string>, config?: Partial<PromptRendererConfig>): (input: {
|
|
9
|
+
sessionID?: string;
|
|
10
|
+
model?: unknown;
|
|
11
|
+
}, output: {
|
|
12
|
+
system: string[];
|
|
13
|
+
}) => void;
|
|
@@ -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
|
@@ -14396,6 +14396,11 @@ var CategoryConfigSchema = exports_external.object({
|
|
|
14396
14396
|
var PluginConfigSchema = exports_external.object({
|
|
14397
14397
|
agents: AgentOverridesSchema.optional(),
|
|
14398
14398
|
categories: exports_external.record(exports_external.string(), CategoryConfigSchema).optional(),
|
|
14399
|
+
fragments: exports_external.record(exports_external.string(), exports_external.array(exports_external.string())).optional(),
|
|
14400
|
+
prompt_renderer: exports_external.object({
|
|
14401
|
+
default: exports_external.enum(["markdown", "xml", "json"]).optional(),
|
|
14402
|
+
model_overrides: exports_external.record(exports_external.string(), exports_external.enum(["markdown", "xml", "json"])).optional()
|
|
14403
|
+
}).optional(),
|
|
14399
14404
|
disabled_agents: exports_external.array(exports_external.string()).optional(),
|
|
14400
14405
|
disabled_hooks: exports_external.array(exports_external.string()).optional(),
|
|
14401
14406
|
disabled_tools: exports_external.array(exports_external.string()).optional(),
|
|
@@ -14819,28 +14824,148 @@ var THINK_MESSAGE = `Extended thinking enabled. Take your time to reason thoroug
|
|
|
14819
14824
|
// src/hooks/rules-injector.ts
|
|
14820
14825
|
import * as fs2 from "fs";
|
|
14821
14826
|
import * as path2 from "path";
|
|
14822
|
-
var
|
|
14823
|
-
|
|
14824
|
-
function loadRules(projectDir) {
|
|
14825
|
-
const rulesPath = path2.join(projectDir, ".opencode", "rules.md");
|
|
14827
|
+
var cache = new Map;
|
|
14828
|
+
function loadCachedFile(filePath) {
|
|
14826
14829
|
try {
|
|
14827
|
-
if (!fs2.existsSync(
|
|
14830
|
+
if (!fs2.existsSync(filePath))
|
|
14828
14831
|
return null;
|
|
14829
|
-
const stat = fs2.statSync(
|
|
14832
|
+
const stat = fs2.statSync(filePath);
|
|
14830
14833
|
const mtime = stat.mtimeMs;
|
|
14831
|
-
|
|
14832
|
-
|
|
14833
|
-
|
|
14834
|
-
const content = fs2.readFileSync(
|
|
14835
|
-
|
|
14836
|
-
cachedRules = { content, mtime };
|
|
14837
|
-
log(`Rules loaded from ${rulesPath}`);
|
|
14834
|
+
const cached2 = cache.get(filePath);
|
|
14835
|
+
if (cached2 && cached2.mtime === mtime)
|
|
14836
|
+
return cached2.content;
|
|
14837
|
+
const content = fs2.readFileSync(filePath, "utf-8");
|
|
14838
|
+
cache.set(filePath, { content, mtime });
|
|
14838
14839
|
return content;
|
|
14839
14840
|
} catch (err) {
|
|
14840
|
-
log(`Error loading
|
|
14841
|
+
log(`Error loading file: ${err}`, { filePath });
|
|
14842
|
+
return null;
|
|
14843
|
+
}
|
|
14844
|
+
}
|
|
14845
|
+
function loadFirstExisting(projectDir, relativePaths) {
|
|
14846
|
+
for (const rel of relativePaths) {
|
|
14847
|
+
const p = path2.join(projectDir, rel);
|
|
14848
|
+
const content = loadCachedFile(p);
|
|
14849
|
+
if (content !== null)
|
|
14850
|
+
return { path: p, content };
|
|
14851
|
+
}
|
|
14852
|
+
return null;
|
|
14853
|
+
}
|
|
14854
|
+
function loadRules(projectDir) {
|
|
14855
|
+
const hit = loadFirstExisting(projectDir, [path2.join(".opencode", "rules.md")]);
|
|
14856
|
+
if (!hit)
|
|
14857
|
+
return null;
|
|
14858
|
+
log(`Rules loaded from ${hit.path}`);
|
|
14859
|
+
return hit.content;
|
|
14860
|
+
}
|
|
14861
|
+
function loadArchitecture(projectDir) {
|
|
14862
|
+
const hit = loadFirstExisting(projectDir, [
|
|
14863
|
+
path2.join(".opencode", "ARCHITECTURE.md"),
|
|
14864
|
+
"ARCHITECTURE.md"
|
|
14865
|
+
]);
|
|
14866
|
+
if (!hit)
|
|
14867
|
+
return null;
|
|
14868
|
+
log(`Architecture loaded from ${hit.path}`);
|
|
14869
|
+
return hit.content;
|
|
14870
|
+
}
|
|
14871
|
+
function loadCodeStyle(projectDir) {
|
|
14872
|
+
const hit = loadFirstExisting(projectDir, [
|
|
14873
|
+
path2.join(".opencode", "CODE_STYLE.md"),
|
|
14874
|
+
"CODE_STYLE.md"
|
|
14875
|
+
]);
|
|
14876
|
+
if (!hit)
|
|
14877
|
+
return null;
|
|
14878
|
+
log(`Code style loaded from ${hit.path}`);
|
|
14879
|
+
return hit.content;
|
|
14880
|
+
}
|
|
14881
|
+
|
|
14882
|
+
// src/hooks/fragment-injector.ts
|
|
14883
|
+
import * as fs3 from "fs";
|
|
14884
|
+
import * as path3 from "path";
|
|
14885
|
+
var cache2 = new Map;
|
|
14886
|
+
async function readCached(absPath) {
|
|
14887
|
+
try {
|
|
14888
|
+
const st = await fs3.promises.stat(absPath);
|
|
14889
|
+
if (!st.isFile())
|
|
14890
|
+
return null;
|
|
14891
|
+
const prev = cache2.get(absPath);
|
|
14892
|
+
if (prev && prev.mtimeMs === st.mtimeMs)
|
|
14893
|
+
return prev.content;
|
|
14894
|
+
const content = await fs3.promises.readFile(absPath, "utf-8");
|
|
14895
|
+
cache2.set(absPath, { mtimeMs: st.mtimeMs, content });
|
|
14896
|
+
return content;
|
|
14897
|
+
} catch (err) {
|
|
14898
|
+
if (err?.code !== "ENOENT") {
|
|
14899
|
+
log("fragment read failed", { path: absPath, error: String(err) });
|
|
14900
|
+
}
|
|
14841
14901
|
return null;
|
|
14842
14902
|
}
|
|
14843
14903
|
}
|
|
14904
|
+
function createFragmentInjector(ctx, internalSessions, fragments) {
|
|
14905
|
+
return async (input, output) => {
|
|
14906
|
+
if (input.sessionID && internalSessions.has(input.sessionID))
|
|
14907
|
+
return;
|
|
14908
|
+
const agent = input.agent;
|
|
14909
|
+
if (!agent || !fragments)
|
|
14910
|
+
return;
|
|
14911
|
+
const fragmentPaths = fragments[agent];
|
|
14912
|
+
if (!Array.isArray(fragmentPaths) || fragmentPaths.length === 0)
|
|
14913
|
+
return;
|
|
14914
|
+
for (const rel of fragmentPaths) {
|
|
14915
|
+
const abs = path3.resolve(ctx.directory, rel);
|
|
14916
|
+
const content = await readCached(abs);
|
|
14917
|
+
if (content === null) {
|
|
14918
|
+
log("fragment file not found; skipping", { agent, path: rel });
|
|
14919
|
+
continue;
|
|
14920
|
+
}
|
|
14921
|
+
output.system.push(content);
|
|
14922
|
+
}
|
|
14923
|
+
};
|
|
14924
|
+
}
|
|
14925
|
+
|
|
14926
|
+
// src/hooks/prompt-renderer.ts
|
|
14927
|
+
function escapeXml(s) {
|
|
14928
|
+
return s.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/\"/g, """).replace(/'/g, "'");
|
|
14929
|
+
}
|
|
14930
|
+
function resolvePromptFormat(inputModel, config2) {
|
|
14931
|
+
const def = config2?.default ?? "markdown";
|
|
14932
|
+
const overrides = config2?.model_overrides ?? {};
|
|
14933
|
+
const providerID = inputModel?.providerID;
|
|
14934
|
+
const modelID = inputModel?.modelID;
|
|
14935
|
+
const key = typeof providerID === "string" && typeof modelID === "string" ? `${providerID}/${modelID}` : undefined;
|
|
14936
|
+
if (key && overrides[key])
|
|
14937
|
+
return overrides[key];
|
|
14938
|
+
if (typeof modelID === "string" && overrides[modelID])
|
|
14939
|
+
return overrides[modelID];
|
|
14940
|
+
return def;
|
|
14941
|
+
}
|
|
14942
|
+
function formatSystemSection(format2, content) {
|
|
14943
|
+
if (format2 === "markdown")
|
|
14944
|
+
return content;
|
|
14945
|
+
if (format2 === "xml") {
|
|
14946
|
+
return `<section name="Rules">${escapeXml(content)}</section>`;
|
|
14947
|
+
}
|
|
14948
|
+
if (format2 === "json") {
|
|
14949
|
+
return JSON.stringify({ section: "Rules", content });
|
|
14950
|
+
}
|
|
14951
|
+
return content;
|
|
14952
|
+
}
|
|
14953
|
+
function createPromptRendererHook(internalSessions, config2) {
|
|
14954
|
+
return (input, output) => {
|
|
14955
|
+
if (input.sessionID && internalSessions.has(input.sessionID))
|
|
14956
|
+
return;
|
|
14957
|
+
if (!Array.isArray(output.system) || output.system.length === 0)
|
|
14958
|
+
return;
|
|
14959
|
+
const format2 = resolvePromptFormat(input.model, config2);
|
|
14960
|
+
if (format2 === "markdown")
|
|
14961
|
+
return;
|
|
14962
|
+
try {
|
|
14963
|
+
output.system = output.system.map((s) => formatSystemSection(format2, s));
|
|
14964
|
+
} catch (err) {
|
|
14965
|
+
log("prompt renderer failed", { error: String(err) });
|
|
14966
|
+
}
|
|
14967
|
+
};
|
|
14968
|
+
}
|
|
14844
14969
|
|
|
14845
14970
|
// node_modules/@opencode-ai/plugin/node_modules/zod/v4/classic/external.js
|
|
14846
14971
|
var exports_external2 = {};
|
|
@@ -15571,15 +15696,15 @@ function mergeDefs2(...defs) {
|
|
|
15571
15696
|
function cloneDef2(schema) {
|
|
15572
15697
|
return mergeDefs2(schema._zod.def);
|
|
15573
15698
|
}
|
|
15574
|
-
function getElementAtPath2(obj,
|
|
15575
|
-
if (!
|
|
15699
|
+
function getElementAtPath2(obj, path4) {
|
|
15700
|
+
if (!path4)
|
|
15576
15701
|
return obj;
|
|
15577
|
-
return
|
|
15702
|
+
return path4.reduce((acc, key) => acc?.[key], obj);
|
|
15578
15703
|
}
|
|
15579
15704
|
function promiseAllObject2(promisesObj) {
|
|
15580
15705
|
const keys = Object.keys(promisesObj);
|
|
15581
|
-
const
|
|
15582
|
-
return Promise.all(
|
|
15706
|
+
const promises2 = keys.map((key) => promisesObj[key]);
|
|
15707
|
+
return Promise.all(promises2).then((results) => {
|
|
15583
15708
|
const resolvedObj = {};
|
|
15584
15709
|
for (let i = 0;i < keys.length; i++) {
|
|
15585
15710
|
resolvedObj[keys[i]] = results[i];
|
|
@@ -15933,11 +16058,11 @@ function aborted2(x, startIndex = 0) {
|
|
|
15933
16058
|
}
|
|
15934
16059
|
return false;
|
|
15935
16060
|
}
|
|
15936
|
-
function prefixIssues2(
|
|
16061
|
+
function prefixIssues2(path4, issues) {
|
|
15937
16062
|
return issues.map((iss) => {
|
|
15938
16063
|
var _a2;
|
|
15939
16064
|
(_a2 = iss).path ?? (_a2.path = []);
|
|
15940
|
-
iss.path.unshift(
|
|
16065
|
+
iss.path.unshift(path4);
|
|
15941
16066
|
return iss;
|
|
15942
16067
|
});
|
|
15943
16068
|
}
|
|
@@ -16105,7 +16230,7 @@ function treeifyError2(error48, _mapper) {
|
|
|
16105
16230
|
return issue3.message;
|
|
16106
16231
|
};
|
|
16107
16232
|
const result = { errors: [] };
|
|
16108
|
-
const processError = (error49,
|
|
16233
|
+
const processError = (error49, path4 = []) => {
|
|
16109
16234
|
var _a2, _b;
|
|
16110
16235
|
for (const issue3 of error49.issues) {
|
|
16111
16236
|
if (issue3.code === "invalid_union" && issue3.errors.length) {
|
|
@@ -16115,7 +16240,7 @@ function treeifyError2(error48, _mapper) {
|
|
|
16115
16240
|
} else if (issue3.code === "invalid_element") {
|
|
16116
16241
|
processError({ issues: issue3.issues }, issue3.path);
|
|
16117
16242
|
} else {
|
|
16118
|
-
const fullpath = [...
|
|
16243
|
+
const fullpath = [...path4, ...issue3.path];
|
|
16119
16244
|
if (fullpath.length === 0) {
|
|
16120
16245
|
result.errors.push(mapper(issue3));
|
|
16121
16246
|
continue;
|
|
@@ -16147,8 +16272,8 @@ function treeifyError2(error48, _mapper) {
|
|
|
16147
16272
|
}
|
|
16148
16273
|
function toDotPath2(_path) {
|
|
16149
16274
|
const segs = [];
|
|
16150
|
-
const
|
|
16151
|
-
for (const seg of
|
|
16275
|
+
const path4 = _path.map((seg) => typeof seg === "object" ? seg.key : seg);
|
|
16276
|
+
for (const seg of path4) {
|
|
16152
16277
|
if (typeof seg === "number")
|
|
16153
16278
|
segs.push(`[${seg}]`);
|
|
16154
16279
|
else if (typeof seg === "symbol")
|
|
@@ -27487,6 +27612,243 @@ Original task:
|
|
|
27487
27612
|
${original}`;
|
|
27488
27613
|
}
|
|
27489
27614
|
|
|
27615
|
+
// src/tools/batch-read.ts
|
|
27616
|
+
import * as fs4 from "fs";
|
|
27617
|
+
import * as path4 from "path";
|
|
27618
|
+
var MAX_PATHS = 20;
|
|
27619
|
+
function resolvePath(projectDir, inputPath) {
|
|
27620
|
+
return path4.isAbsolute(inputPath) ? inputPath : path4.join(projectDir, inputPath);
|
|
27621
|
+
}
|
|
27622
|
+
function isProbablyBinary(buf) {
|
|
27623
|
+
const sample = buf.subarray(0, Math.min(buf.length, 8192));
|
|
27624
|
+
for (const b of sample) {
|
|
27625
|
+
if (b === 0)
|
|
27626
|
+
return true;
|
|
27627
|
+
}
|
|
27628
|
+
return false;
|
|
27629
|
+
}
|
|
27630
|
+
async function readOne(projectDir, inputPath) {
|
|
27631
|
+
const abs = resolvePath(projectDir, inputPath);
|
|
27632
|
+
try {
|
|
27633
|
+
const stat = await fs4.promises.stat(abs);
|
|
27634
|
+
if (!stat.isFile()) {
|
|
27635
|
+
return `=== ${inputPath} ===
|
|
27636
|
+
[File not found]
|
|
27637
|
+
`;
|
|
27638
|
+
}
|
|
27639
|
+
const buf = await fs4.promises.readFile(abs);
|
|
27640
|
+
if (isProbablyBinary(buf)) {
|
|
27641
|
+
return `=== ${inputPath} ===
|
|
27642
|
+
[Binary file]
|
|
27643
|
+
`;
|
|
27644
|
+
}
|
|
27645
|
+
const content = buf.toString("utf-8");
|
|
27646
|
+
return `=== ${inputPath} ===
|
|
27647
|
+
${content}
|
|
27648
|
+
`;
|
|
27649
|
+
} catch (err) {
|
|
27650
|
+
if (err?.code === "ENOENT") {
|
|
27651
|
+
return `=== ${inputPath} ===
|
|
27652
|
+
[File not found]
|
|
27653
|
+
`;
|
|
27654
|
+
}
|
|
27655
|
+
log("batch_read failed to read file", { path: abs, error: String(err) });
|
|
27656
|
+
return `=== ${inputPath} ===
|
|
27657
|
+
[File not found]
|
|
27658
|
+
`;
|
|
27659
|
+
}
|
|
27660
|
+
}
|
|
27661
|
+
function createBatchReadTool(ctx) {
|
|
27662
|
+
return tool({
|
|
27663
|
+
description: "Read multiple files in parallel and concatenate contents.",
|
|
27664
|
+
args: {
|
|
27665
|
+
paths: tool.schema.array(tool.schema.string()).describe("File paths (max 20).")
|
|
27666
|
+
},
|
|
27667
|
+
execute: async ({ paths }) => {
|
|
27668
|
+
if (!Array.isArray(paths)) {
|
|
27669
|
+
return "Error: paths must be an array of strings";
|
|
27670
|
+
}
|
|
27671
|
+
if (paths.length > MAX_PATHS) {
|
|
27672
|
+
return `Error: maximum ${MAX_PATHS} paths allowed`;
|
|
27673
|
+
}
|
|
27674
|
+
const results = await Promise.all(paths.map((p) => readOne(ctx.directory, p)));
|
|
27675
|
+
return results.join("");
|
|
27676
|
+
}
|
|
27677
|
+
});
|
|
27678
|
+
}
|
|
27679
|
+
|
|
27680
|
+
// src/tools/continuity-ledger.ts
|
|
27681
|
+
import * as fs5 from "fs";
|
|
27682
|
+
import * as path5 from "path";
|
|
27683
|
+
function normalizeLedgerName(name) {
|
|
27684
|
+
const trimmed = (name ?? "").trim().replace(/\.md$/i, "");
|
|
27685
|
+
const spaced = trimmed.replace(/\s+/g, "-");
|
|
27686
|
+
const cleaned = spaced.replace(/[^A-Za-z0-9._-]/g, "");
|
|
27687
|
+
return cleaned.length > 0 ? cleaned : "ledger";
|
|
27688
|
+
}
|
|
27689
|
+
function ledgerDir(projectDir) {
|
|
27690
|
+
return path5.join(projectDir, ".opencode", "ledgers");
|
|
27691
|
+
}
|
|
27692
|
+
function ledgerPath(projectDir, name) {
|
|
27693
|
+
return path5.join(ledgerDir(projectDir), `${normalizeLedgerName(name)}.md`);
|
|
27694
|
+
}
|
|
27695
|
+
function createLedgerSaveTool(ctx) {
|
|
27696
|
+
return tool({
|
|
27697
|
+
description: "Save a continuity ledger markdown document to .opencode/ledgers/.",
|
|
27698
|
+
args: {
|
|
27699
|
+
name: tool.schema.string().describe('Ledger name (e.g. "auth-refactor").'),
|
|
27700
|
+
content: tool.schema.string().describe("Markdown content.")
|
|
27701
|
+
},
|
|
27702
|
+
execute: async ({ name, content }) => {
|
|
27703
|
+
const dir = ledgerDir(ctx.directory);
|
|
27704
|
+
const outPath = ledgerPath(ctx.directory, name);
|
|
27705
|
+
try {
|
|
27706
|
+
await fs5.promises.mkdir(dir, { recursive: true });
|
|
27707
|
+
await fs5.promises.writeFile(outPath, content, "utf-8");
|
|
27708
|
+
return `Saved to .opencode/ledgers/${path5.basename(outPath)}`;
|
|
27709
|
+
} catch (err) {
|
|
27710
|
+
log("ledger_save failed", { error: String(err) });
|
|
27711
|
+
return `Error: failed to save ledger`;
|
|
27712
|
+
}
|
|
27713
|
+
}
|
|
27714
|
+
});
|
|
27715
|
+
}
|
|
27716
|
+
function createLedgerLoadTool(ctx) {
|
|
27717
|
+
return tool({
|
|
27718
|
+
description: "Load a continuity ledger markdown document from .opencode/ledgers/.",
|
|
27719
|
+
args: {
|
|
27720
|
+
name: tool.schema.string().optional().describe("Ledger name (defaults to most recent).")
|
|
27721
|
+
},
|
|
27722
|
+
execute: async ({ name }) => {
|
|
27723
|
+
const dir = ledgerDir(ctx.directory);
|
|
27724
|
+
try {
|
|
27725
|
+
if (name && name.trim().length > 0) {
|
|
27726
|
+
const p = ledgerPath(ctx.directory, name);
|
|
27727
|
+
if (!fs5.existsSync(p))
|
|
27728
|
+
return "No ledgers found";
|
|
27729
|
+
return await fs5.promises.readFile(p, "utf-8");
|
|
27730
|
+
}
|
|
27731
|
+
if (!fs5.existsSync(dir))
|
|
27732
|
+
return "No ledgers found";
|
|
27733
|
+
const entries = await fs5.promises.readdir(dir);
|
|
27734
|
+
const files = entries.filter((e) => e.toLowerCase().endsWith(".md")).map((e) => path5.join(dir, e));
|
|
27735
|
+
if (files.length === 0)
|
|
27736
|
+
return "No ledgers found";
|
|
27737
|
+
let latest = files[0];
|
|
27738
|
+
let latestMtime = -1;
|
|
27739
|
+
for (const f of files) {
|
|
27740
|
+
try {
|
|
27741
|
+
const st = await fs5.promises.stat(f);
|
|
27742
|
+
if (st.mtimeMs > latestMtime) {
|
|
27743
|
+
latestMtime = st.mtimeMs;
|
|
27744
|
+
latest = f;
|
|
27745
|
+
}
|
|
27746
|
+
} catch {}
|
|
27747
|
+
}
|
|
27748
|
+
return await fs5.promises.readFile(latest, "utf-8");
|
|
27749
|
+
} catch (err) {
|
|
27750
|
+
log("ledger_load failed", { error: String(err) });
|
|
27751
|
+
return "No ledgers found";
|
|
27752
|
+
}
|
|
27753
|
+
}
|
|
27754
|
+
});
|
|
27755
|
+
}
|
|
27756
|
+
|
|
27757
|
+
// src/tools/ast-search.ts
|
|
27758
|
+
import * as fs6 from "fs";
|
|
27759
|
+
import * as path6 from "path";
|
|
27760
|
+
import { spawnSync } from "child_process";
|
|
27761
|
+
var MAX_MATCHES = 50;
|
|
27762
|
+
function stripAnsi(s) {
|
|
27763
|
+
return s.replace(/\u001b\[[0-9;]*m/g, "");
|
|
27764
|
+
}
|
|
27765
|
+
function resolveSearchPath(projectDir, p) {
|
|
27766
|
+
return path6.isAbsolute(p) ? p : path6.join(projectDir, p);
|
|
27767
|
+
}
|
|
27768
|
+
function candidatesFor(bin) {
|
|
27769
|
+
const out = [bin];
|
|
27770
|
+
if (process.platform === "win32") {
|
|
27771
|
+
out.push(`${bin}.exe`, `${bin}.cmd`, `${bin}.bat`);
|
|
27772
|
+
}
|
|
27773
|
+
return out;
|
|
27774
|
+
}
|
|
27775
|
+
function findAstGrepBinary() {
|
|
27776
|
+
const pathEnv = process.env.PATH ?? "";
|
|
27777
|
+
const dirs = pathEnv.split(path6.delimiter).filter(Boolean);
|
|
27778
|
+
const bins = ["sg", "ast-grep"];
|
|
27779
|
+
for (const dir of dirs) {
|
|
27780
|
+
for (const b of bins) {
|
|
27781
|
+
for (const cand of candidatesFor(b)) {
|
|
27782
|
+
const full = path6.join(dir, cand);
|
|
27783
|
+
try {
|
|
27784
|
+
const st = fs6.statSync(full);
|
|
27785
|
+
if (!st.isFile())
|
|
27786
|
+
continue;
|
|
27787
|
+
fs6.accessSync(full, fs6.constants.X_OK);
|
|
27788
|
+
return full;
|
|
27789
|
+
} catch {}
|
|
27790
|
+
}
|
|
27791
|
+
}
|
|
27792
|
+
}
|
|
27793
|
+
return null;
|
|
27794
|
+
}
|
|
27795
|
+
function normalizeOutput(stdout) {
|
|
27796
|
+
const lines = stripAnsi(stdout).split(`
|
|
27797
|
+
`).map((l) => l.trimEnd()).filter((l) => l.length > 0);
|
|
27798
|
+
const out = [];
|
|
27799
|
+
for (const line of lines) {
|
|
27800
|
+
const m3 = line.match(/^(.*?):(\d+):(\d+):(.*)$/);
|
|
27801
|
+
if (m3) {
|
|
27802
|
+
out.push(`${m3[1]}:${m3[2]}: ${m3[4].trim()}`);
|
|
27803
|
+
continue;
|
|
27804
|
+
}
|
|
27805
|
+
const m2 = line.match(/^(.*?):(\d+):(.*)$/);
|
|
27806
|
+
if (m2) {
|
|
27807
|
+
out.push(`${m2[1]}:${m2[2]}: ${m2[3].trim()}`);
|
|
27808
|
+
continue;
|
|
27809
|
+
}
|
|
27810
|
+
out.push(line);
|
|
27811
|
+
}
|
|
27812
|
+
return out.slice(0, MAX_MATCHES).join(`
|
|
27813
|
+
`);
|
|
27814
|
+
}
|
|
27815
|
+
function createAstSearchTool(ctx, binaryPath) {
|
|
27816
|
+
return tool({
|
|
27817
|
+
description: "AST-aware code search using ast-grep (sg).",
|
|
27818
|
+
args: {
|
|
27819
|
+
pattern: tool.schema.string().describe('ast-grep pattern (e.g. "console.log($$$)").'),
|
|
27820
|
+
path: tool.schema.string().optional().describe("Directory to search (default: project root)."),
|
|
27821
|
+
lang: tool.schema.string().optional().describe('Language (e.g. "typescript", "python").')
|
|
27822
|
+
},
|
|
27823
|
+
execute: async ({ pattern, path: searchPath, lang }) => {
|
|
27824
|
+
const target = resolveSearchPath(ctx.directory, searchPath ?? ".");
|
|
27825
|
+
const args = ["--pattern", pattern, target];
|
|
27826
|
+
if (lang && lang.trim().length > 0) {
|
|
27827
|
+
args.push("--lang", lang);
|
|
27828
|
+
}
|
|
27829
|
+
const res = spawnSync(binaryPath, args, {
|
|
27830
|
+
cwd: ctx.directory,
|
|
27831
|
+
encoding: "utf-8"
|
|
27832
|
+
});
|
|
27833
|
+
if (res.error) {
|
|
27834
|
+
log("ast_search spawn failed", { error: String(res.error) });
|
|
27835
|
+
return `Error: ${String(res.error)}`;
|
|
27836
|
+
}
|
|
27837
|
+
const stdout = res.stdout ?? "";
|
|
27838
|
+
const stderr = res.stderr ?? "";
|
|
27839
|
+
if ((res.status ?? 0) === 1 && stdout.trim().length === 0) {
|
|
27840
|
+
return "No matches";
|
|
27841
|
+
}
|
|
27842
|
+
if ((res.status ?? 0) !== 0 && (res.status ?? 0) !== 1) {
|
|
27843
|
+
log("ast_search failed", { status: res.status, stderr });
|
|
27844
|
+
return stderr.trim().length > 0 ? stderr.trim() : "Error: ast_search failed";
|
|
27845
|
+
}
|
|
27846
|
+
const normalized = normalizeOutput(stdout);
|
|
27847
|
+
return normalized.length > 0 ? normalized : "No matches";
|
|
27848
|
+
}
|
|
27849
|
+
});
|
|
27850
|
+
}
|
|
27851
|
+
|
|
27490
27852
|
// src/hooks/todo-enforcer.ts
|
|
27491
27853
|
var DEFAULT_MAX_ENFORCEMENTS = 5;
|
|
27492
27854
|
var sessionState = new Map;
|
|
@@ -27569,7 +27931,7 @@ ${unfinished.map((t) => `- [ ] ${t.text}`).join(`
|
|
|
27569
27931
|
}
|
|
27570
27932
|
|
|
27571
27933
|
// src/hooks/comment-checker.ts
|
|
27572
|
-
import * as
|
|
27934
|
+
import * as fs7 from "fs";
|
|
27573
27935
|
var AI_SLOP_PATTERNS = [
|
|
27574
27936
|
/\/\/ .{80,}/,
|
|
27575
27937
|
/\/\/ (This|The|We|Here|Note:)/i,
|
|
@@ -27639,7 +28001,8 @@ function buildWarning(ratio, slopCount, maxRatio, slopThreshold) {
|
|
|
27639
28001
|
}
|
|
27640
28002
|
function createCommentCheckerHook(internalSessions, maxRatio = 0.3, slopThreshold = 3) {
|
|
27641
28003
|
return (input, output) => {
|
|
27642
|
-
|
|
28004
|
+
const toolName = typeof input.tool === "string" ? input.tool : input.tool.name;
|
|
28005
|
+
if (toolName !== "Write" && toolName !== "Edit")
|
|
27643
28006
|
return;
|
|
27644
28007
|
if (internalSessions.has(input.sessionID))
|
|
27645
28008
|
return;
|
|
@@ -27647,7 +28010,7 @@ function createCommentCheckerHook(internalSessions, maxRatio = 0.3, slopThreshol
|
|
|
27647
28010
|
if (!filePath || !isCodeFile(filePath))
|
|
27648
28011
|
return;
|
|
27649
28012
|
try {
|
|
27650
|
-
const content =
|
|
28013
|
+
const content = fs7.readFileSync(filePath, "utf-8");
|
|
27651
28014
|
const result = checkComments(content, filePath, maxRatio, slopThreshold);
|
|
27652
28015
|
if (result.shouldWarn) {
|
|
27653
28016
|
output.output += `
|
|
@@ -27662,6 +28025,150 @@ function createCommentCheckerHook(internalSessions, maxRatio = 0.3, slopThreshol
|
|
|
27662
28025
|
};
|
|
27663
28026
|
}
|
|
27664
28027
|
|
|
28028
|
+
// src/hooks/token-truncation.ts
|
|
28029
|
+
var DEFAULT_MAX_CHARS = 30000;
|
|
28030
|
+
function truncateMiddle(input, maxChars) {
|
|
28031
|
+
if (!Number.isFinite(maxChars) || maxChars <= 0)
|
|
28032
|
+
return input;
|
|
28033
|
+
if (input.length <= maxChars)
|
|
28034
|
+
return input;
|
|
28035
|
+
if (maxChars < 32)
|
|
28036
|
+
return input.slice(0, maxChars);
|
|
28037
|
+
const len = input.length;
|
|
28038
|
+
let head = Math.floor(maxChars * 0.4);
|
|
28039
|
+
let tail = Math.floor(maxChars * 0.4);
|
|
28040
|
+
head = Math.min(Math.max(head, 1), maxChars);
|
|
28041
|
+
const buildMarker = (removed2) => `
|
|
28042
|
+
|
|
28043
|
+
... [truncated ${removed2} chars] ...
|
|
28044
|
+
|
|
28045
|
+
`;
|
|
28046
|
+
const finalize2 = (h, t) => {
|
|
28047
|
+
const removed2 = Math.max(0, len - h - t);
|
|
28048
|
+
const marker2 = buildMarker(removed2);
|
|
28049
|
+
const availableTail2 = maxChars - h - marker2.length;
|
|
28050
|
+
const finalTail = Math.max(0, Math.min(t, availableTail2));
|
|
28051
|
+
const finalRemoved = Math.max(0, len - h - finalTail);
|
|
28052
|
+
const finalMarker = buildMarker(finalRemoved);
|
|
28053
|
+
const availableTail22 = maxChars - h - finalMarker.length;
|
|
28054
|
+
const finalTail2 = Math.max(0, Math.min(finalTail, availableTail22));
|
|
28055
|
+
const removed22 = Math.max(0, len - h - finalTail2);
|
|
28056
|
+
const marker22 = buildMarker(removed22);
|
|
28057
|
+
const budget = h + marker22.length + finalTail2;
|
|
28058
|
+
if (budget > maxChars) {
|
|
28059
|
+
return input.slice(0, maxChars);
|
|
28060
|
+
}
|
|
28061
|
+
return input.slice(0, h) + marker22 + input.slice(len - finalTail2);
|
|
28062
|
+
};
|
|
28063
|
+
const removed = Math.max(0, len - head - tail);
|
|
28064
|
+
const marker = buildMarker(removed);
|
|
28065
|
+
const availableTail = maxChars - head - marker.length;
|
|
28066
|
+
if (availableTail < 0) {
|
|
28067
|
+
const shrinkHead = Math.max(0, maxChars - marker.length);
|
|
28068
|
+
return finalize2(shrinkHead, 0);
|
|
28069
|
+
}
|
|
28070
|
+
tail = Math.min(tail, availableTail);
|
|
28071
|
+
return finalize2(head, tail);
|
|
28072
|
+
}
|
|
28073
|
+
function createTokenTruncationHook(internalSessions, maxChars = DEFAULT_MAX_CHARS) {
|
|
28074
|
+
const budget = Number.isFinite(maxChars) ? Math.max(1, Math.floor(maxChars)) : DEFAULT_MAX_CHARS;
|
|
28075
|
+
return (input, output) => {
|
|
28076
|
+
if (internalSessions.has(input.sessionID))
|
|
28077
|
+
return;
|
|
28078
|
+
const before = output.output;
|
|
28079
|
+
const after = truncateMiddle(before, budget);
|
|
28080
|
+
if (after !== before) {
|
|
28081
|
+
output.output = after;
|
|
28082
|
+
const toolName = typeof input.tool === "string" ? input.tool : input.tool.name;
|
|
28083
|
+
log("Token truncation applied", { tool: toolName, before: before.length, after: after.length });
|
|
28084
|
+
}
|
|
28085
|
+
};
|
|
28086
|
+
}
|
|
28087
|
+
|
|
28088
|
+
// src/hooks/session-compaction.ts
|
|
28089
|
+
function partsToText(parts) {
|
|
28090
|
+
if (!parts)
|
|
28091
|
+
return "";
|
|
28092
|
+
return parts.filter((p) => p && p.type === "text" && typeof p.text === "string").map((p) => p.text).join("");
|
|
28093
|
+
}
|
|
28094
|
+
function getRole(m) {
|
|
28095
|
+
return (m.role ?? m.info?.role ?? "").toLowerCase();
|
|
28096
|
+
}
|
|
28097
|
+
function getText(m) {
|
|
28098
|
+
if (typeof m.content === "string")
|
|
28099
|
+
return m.content;
|
|
28100
|
+
return partsToText(m.parts);
|
|
28101
|
+
}
|
|
28102
|
+
function uniq(items) {
|
|
28103
|
+
const seen = new Set;
|
|
28104
|
+
const out = [];
|
|
28105
|
+
for (const it of items) {
|
|
28106
|
+
const k = typeof it === "string" ? it : JSON.stringify(it);
|
|
28107
|
+
if (seen.has(k))
|
|
28108
|
+
continue;
|
|
28109
|
+
seen.add(k);
|
|
28110
|
+
out.push(it);
|
|
28111
|
+
}
|
|
28112
|
+
return out;
|
|
28113
|
+
}
|
|
28114
|
+
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;
|
|
28115
|
+
function buildSessionSummary(messages) {
|
|
28116
|
+
const assistantTexts = messages.filter((m) => getRole(m) === "assistant").map((m) => getText(m)).map((t) => t.trim()).filter(Boolean);
|
|
28117
|
+
const combined = assistantTexts.join(`
|
|
28118
|
+
`);
|
|
28119
|
+
const files = uniq((combined.match(FILE_PATH_RE) ?? []).map((s) => s.replace(/^[`"']|[`"']$/g, "")));
|
|
28120
|
+
const decisionLines = uniq(assistantTexts.flatMap((t) => t.split(`
|
|
28121
|
+
`)).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));
|
|
28122
|
+
const nextLines = uniq(assistantTexts.flatMap((t) => t.split(`
|
|
28123
|
+
`)).map((l) => l.trim()).filter((l) => /(next\s*steps?|todo|next:|\u6B21\s*\u306B|\u3084\u308B|TODO)/i.test(l)).slice(0, 10));
|
|
28124
|
+
const currentState = (assistantTexts[assistantTexts.length - 1] ?? "").slice(0, 800).trim();
|
|
28125
|
+
const lines = [];
|
|
28126
|
+
lines.push("## Session Summary");
|
|
28127
|
+
lines.push("");
|
|
28128
|
+
lines.push("### Key Decisions");
|
|
28129
|
+
if (decisionLines.length === 0)
|
|
28130
|
+
lines.push("- (none)");
|
|
28131
|
+
else
|
|
28132
|
+
for (const l of decisionLines)
|
|
28133
|
+
lines.push(`- ${l}`);
|
|
28134
|
+
lines.push("");
|
|
28135
|
+
lines.push("### Files Modified");
|
|
28136
|
+
if (files.length === 0)
|
|
28137
|
+
lines.push("- (none)");
|
|
28138
|
+
else
|
|
28139
|
+
for (const f of files)
|
|
28140
|
+
lines.push(`- ${f}`);
|
|
28141
|
+
lines.push("");
|
|
28142
|
+
lines.push("### Current State");
|
|
28143
|
+
lines.push(currentState ? `- ${currentState}` : "- (none)");
|
|
28144
|
+
lines.push("");
|
|
28145
|
+
lines.push("### Next Steps");
|
|
28146
|
+
if (nextLines.length === 0)
|
|
28147
|
+
lines.push("- (none)");
|
|
28148
|
+
else
|
|
28149
|
+
for (const l of nextLines)
|
|
28150
|
+
lines.push(`- ${l}`);
|
|
28151
|
+
lines.push("");
|
|
28152
|
+
return lines.join(`
|
|
28153
|
+
`);
|
|
28154
|
+
}
|
|
28155
|
+
function createSessionCompactionHook(ctx, internalSessions) {
|
|
28156
|
+
return async (input, output) => {
|
|
28157
|
+
if (internalSessions.has(input.sessionID))
|
|
28158
|
+
return;
|
|
28159
|
+
try {
|
|
28160
|
+
const res = await ctx.client?.session?.listMessages?.({ id: input.sessionID });
|
|
28161
|
+
const msgs = res?.messages ?? res ?? [];
|
|
28162
|
+
const summary = buildSessionSummary(msgs);
|
|
28163
|
+
output.context.push(summary);
|
|
28164
|
+
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.";
|
|
28165
|
+
} catch (err) {
|
|
28166
|
+
log("Session compaction hook failed", { error: err });
|
|
28167
|
+
output.prompt = "Summarize the session using this exact template: ## Session Summary; ### Key Decisions; ### Files Modified; ### Current State; ### Next Steps.";
|
|
28168
|
+
}
|
|
28169
|
+
};
|
|
28170
|
+
}
|
|
28171
|
+
|
|
27665
28172
|
// src/concurrency/semaphore.ts
|
|
27666
28173
|
class Semaphore {
|
|
27667
28174
|
max;
|
|
@@ -27675,10 +28182,10 @@ class Semaphore {
|
|
|
27675
28182
|
this.active++;
|
|
27676
28183
|
return;
|
|
27677
28184
|
}
|
|
27678
|
-
return new Promise((
|
|
28185
|
+
return new Promise((resolve2) => {
|
|
27679
28186
|
this.queue.push(() => {
|
|
27680
28187
|
this.active++;
|
|
27681
|
-
|
|
28188
|
+
resolve2();
|
|
27682
28189
|
});
|
|
27683
28190
|
});
|
|
27684
28191
|
}
|
|
@@ -27798,8 +28305,17 @@ var OpenCodeUltra = async (ctx) => {
|
|
|
27798
28305
|
resolveAgentModel
|
|
27799
28306
|
});
|
|
27800
28307
|
const ralphTools = createRalphLoopTools(ctx, internalSessions);
|
|
28308
|
+
const batchRead = createBatchReadTool(ctx);
|
|
28309
|
+
const ledgerSave = createLedgerSaveTool(ctx);
|
|
28310
|
+
const ledgerLoad = createLedgerLoadTool(ctx);
|
|
28311
|
+
const astGrepBin = findAstGrepBinary();
|
|
28312
|
+
const astSearch = astGrepBin ? createAstSearchTool(ctx, astGrepBin) : null;
|
|
27801
28313
|
const todoEnforcer = createTodoEnforcer(ctx, internalSessions, pluginConfig.todo_enforcer?.maxEnforcements);
|
|
27802
28314
|
const commentCheckerHook = createCommentCheckerHook(internalSessions, pluginConfig.comment_checker?.maxRatio, pluginConfig.comment_checker?.slopThreshold);
|
|
28315
|
+
const tokenTruncationHook = createTokenTruncationHook(internalSessions, pluginConfig.token_truncation?.maxChars);
|
|
28316
|
+
const sessionCompactionHook = createSessionCompactionHook(ctx, internalSessions);
|
|
28317
|
+
const fragmentInjector = createFragmentInjector(ctx, internalSessions, pluginConfig.fragments);
|
|
28318
|
+
const promptRendererHook = createPromptRendererHook(internalSessions, pluginConfig.prompt_renderer);
|
|
27803
28319
|
const pendingKeywords = new Map;
|
|
27804
28320
|
log("Config loaded", {
|
|
27805
28321
|
agentCount: Object.keys(agents).length,
|
|
@@ -27820,6 +28336,18 @@ var OpenCodeUltra = async (ctx) => {
|
|
|
27820
28336
|
if (!disabledTools.has("cancel_ralph")) {
|
|
27821
28337
|
toolRegistry.cancel_ralph = ralphTools.cancel_ralph;
|
|
27822
28338
|
}
|
|
28339
|
+
if (!disabledTools.has("batch_read")) {
|
|
28340
|
+
toolRegistry.batch_read = batchRead;
|
|
28341
|
+
}
|
|
28342
|
+
if (!disabledTools.has("ledger_save")) {
|
|
28343
|
+
toolRegistry.ledger_save = ledgerSave;
|
|
28344
|
+
}
|
|
28345
|
+
if (!disabledTools.has("ledger_load")) {
|
|
28346
|
+
toolRegistry.ledger_load = ledgerLoad;
|
|
28347
|
+
}
|
|
28348
|
+
if (!disabledTools.has("ast_search") && astSearch) {
|
|
28349
|
+
toolRegistry.ast_search = astSearch;
|
|
28350
|
+
}
|
|
27823
28351
|
return {
|
|
27824
28352
|
tool: toolRegistry,
|
|
27825
28353
|
config: async (config3) => {
|
|
@@ -27844,6 +28372,15 @@ var OpenCodeUltra = async (ctx) => {
|
|
|
27844
28372
|
...existingAgents,
|
|
27845
28373
|
...agentConfigs
|
|
27846
28374
|
};
|
|
28375
|
+
if (pluginConfig.demote_builtin !== false) {
|
|
28376
|
+
const agentMap = config3.agent;
|
|
28377
|
+
for (const name of ["build", "plan", "triage", "docs"]) {
|
|
28378
|
+
const cur = agentMap[name];
|
|
28379
|
+
if (cur && typeof cur === "object") {
|
|
28380
|
+
agentMap[name] = { ...cur, mode: "subagent" };
|
|
28381
|
+
}
|
|
28382
|
+
}
|
|
28383
|
+
}
|
|
27847
28384
|
log("Agents registered", { agents: Object.keys(agentConfigs) });
|
|
27848
28385
|
},
|
|
27849
28386
|
"chat.message": async (input, output) => {
|
|
@@ -27905,11 +28442,39 @@ var OpenCodeUltra = async (ctx) => {
|
|
|
27905
28442
|
${rules}`);
|
|
27906
28443
|
}
|
|
27907
28444
|
}
|
|
28445
|
+
if (!disabledHooks.has("context-injector")) {
|
|
28446
|
+
const arch = loadArchitecture(ctx.directory);
|
|
28447
|
+
if (arch) {
|
|
28448
|
+
output.system.push(`## Architecture
|
|
28449
|
+
|
|
28450
|
+
${arch}`);
|
|
28451
|
+
}
|
|
28452
|
+
const style = loadCodeStyle(ctx.directory);
|
|
28453
|
+
if (style) {
|
|
28454
|
+
output.system.push(`## Code Style
|
|
28455
|
+
|
|
28456
|
+
${style}`);
|
|
28457
|
+
}
|
|
28458
|
+
}
|
|
28459
|
+
if (!disabledHooks.has("fragment-injector")) {
|
|
28460
|
+
await fragmentInjector(input, output);
|
|
28461
|
+
}
|
|
28462
|
+
if (!disabledHooks.has("prompt-renderer")) {
|
|
28463
|
+
promptRendererHook(input, output);
|
|
28464
|
+
}
|
|
28465
|
+
},
|
|
28466
|
+
"experimental.session.compacting": async (input, output) => {
|
|
28467
|
+
if (disabledHooks.has("session-compaction"))
|
|
28468
|
+
return;
|
|
28469
|
+
await sessionCompactionHook(input, output);
|
|
27908
28470
|
},
|
|
27909
28471
|
"tool.execute.after": async (input, output) => {
|
|
27910
28472
|
if (!disabledHooks.has("comment-checker")) {
|
|
27911
28473
|
commentCheckerHook(input, output);
|
|
27912
28474
|
}
|
|
28475
|
+
if (!disabledHooks.has("token-truncation")) {
|
|
28476
|
+
tokenTruncationHook(input, output);
|
|
28477
|
+
}
|
|
27913
28478
|
},
|
|
27914
28479
|
event: async ({ event }) => {
|
|
27915
28480
|
if (event.type === "session.deleted") {
|
package/package.json
CHANGED