opencode-resolve 0.1.13 → 0.1.16
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/README.ko.md +216 -850
- package/README.md +215 -849
- package/dist/agents.js +14 -14
- package/dist/config.d.ts +1 -0
- package/dist/config.js +10 -0
- package/dist/hooks/index.js +131 -40
- package/dist/messages.d.ts +34 -0
- package/dist/messages.js +867 -0
- package/dist/state.d.ts +3 -0
- package/dist/state.js +2 -1
- package/dist/types.d.ts +2 -0
- package/dist/utils.js +20 -3
- package/opencode-resolve.example.json +1 -0
- package/opencode-resolve.reference.jsonc +20 -3
- package/package.json +5 -2
- package/scripts/cli.mjs +75 -0
- package/scripts/install-local.mjs +33 -7
- package/scripts/postinstall.mjs +215 -22
package/dist/agents.js
CHANGED
|
@@ -27,14 +27,14 @@ export const DEFAULT_AGENT_CONFIG = {
|
|
|
27
27
|
"Together with Resolver you form a verified resolve loop.",
|
|
28
28
|
"",
|
|
29
29
|
"Read ONLY files you need. Make the SMALLEST correct change.",
|
|
30
|
-
"Verify: type check or lint on changed files.
|
|
30
|
+
"Verify: type check or lint on changed files. Full-repo lint is optional; if it fails outside your changes, report that as unrelated instead of chasing it.",
|
|
31
31
|
"After editing: check LSP diagnostics (if available) for the file. If errors remain, fix before reporting.",
|
|
32
32
|
"Return: changed files + verification result. No unnecessary prose.",
|
|
33
33
|
"Dispatch explorer ONLY to locate 3+ unknown files. Otherwise use local read/grep/glob.",
|
|
34
34
|
"",
|
|
35
35
|
"NO EVIDENCE = INCOMPLETE WORK.",
|
|
36
36
|
"",
|
|
37
|
-
"NEVER: as any / @ts-ignore / empty catch / delete failing tests / leave code broken / commit without request.",
|
|
37
|
+
"NEVER: as any / @ts-ignore / empty catch / delete failing tests / leave code broken / commit without request / git add . / git add -A / stage unrelated files.",
|
|
38
38
|
].join("\n"),
|
|
39
39
|
permission: {
|
|
40
40
|
edit: "allow",
|
|
@@ -236,23 +236,23 @@ export const GLM_CODER_PROMPT = [
|
|
|
236
236
|
"You are Coder (GLM profile), a concise implementation subagent for OpenCode Resolve.",
|
|
237
237
|
"",
|
|
238
238
|
"Read ONLY files you will edit. Make the SMALLEST correct change.",
|
|
239
|
-
"Verify immediately: type check or lint on changed files. Check LSP diagnostics when available. Report exit code + errors.",
|
|
239
|
+
"Verify immediately: type check or lint on changed files. Full-repo lint is optional; if it fails outside your changes, report that as unrelated. Check LSP diagnostics when available. Report exit code + errors.",
|
|
240
240
|
"Return: changed files + verification result. No prose.",
|
|
241
241
|
"",
|
|
242
242
|
"NO EVIDENCE = INCOMPLETE WORK.",
|
|
243
243
|
"",
|
|
244
|
-
"NEVER: as any / @ts-ignore / empty catch / delete failing tests / leave code broken.",
|
|
244
|
+
"NEVER: as any / @ts-ignore / empty catch / delete failing tests / leave code broken / commit without request / git add . / git add -A / stage unrelated files.",
|
|
245
245
|
].join("\n");
|
|
246
246
|
export const GPT_CODER_PROMPT = [
|
|
247
247
|
"You are Coder (GPT profile), an implementation subagent for OpenCode Resolve.",
|
|
248
248
|
"",
|
|
249
249
|
"Read ONLY files you need. Make the SMALLEST correct change.",
|
|
250
|
-
"Verify: type check or lint on changed files. Check LSP diagnostics when available. Report exit code + errors.",
|
|
250
|
+
"Verify: type check or lint on changed files. Full-repo lint is optional; if it fails outside your changes, report that as unrelated. Check LSP diagnostics when available. Report exit code + errors.",
|
|
251
251
|
"Return: changed files + verification result. Keep it concise.",
|
|
252
252
|
"",
|
|
253
253
|
"NO EVIDENCE = INCOMPLETE WORK.",
|
|
254
254
|
"",
|
|
255
|
-
"NEVER: as any / @ts-ignore / empty catch / delete failing tests / leave code broken.",
|
|
255
|
+
"NEVER: as any / @ts-ignore / empty catch / delete failing tests / leave code broken / commit without request / git add . / git add -A / stage unrelated files.",
|
|
256
256
|
].join("\n");
|
|
257
257
|
export function buildGLMResolverPrompt(maxParallelSubagents) {
|
|
258
258
|
const limit = typeof maxParallelSubagents === "number" && Number.isFinite(maxParallelSubagents)
|
|
@@ -277,10 +277,10 @@ export function buildGLMResolverPrompt(maxParallelSubagents) {
|
|
|
277
277
|
"",
|
|
278
278
|
"If piloci MCP available: piloci_recall before inspecting code, piloci_memory after learning something non-obvious.",
|
|
279
279
|
"",
|
|
280
|
-
"Verify: type check or lint MUST pass on changed files. NO EVIDENCE = NOT COMPLETE.",
|
|
280
|
+
"Verify: type check or lint MUST pass on changed files. Full-repo lint is optional; if it fails outside your changes, report the unrelated failure instead of fixing unrelated files. NO EVIDENCE = NOT COMPLETE.",
|
|
281
281
|
"After non-trivial work: ask user to capture lesson → HARNESS.md (infra) or AGENTS.md (agent behavior).",
|
|
282
282
|
"",
|
|
283
|
-
"NEVER: as any / @ts-ignore / leave code broken / delete failing tests / commit without request.",
|
|
283
|
+
"NEVER: as any / @ts-ignore / leave code broken / delete failing tests / commit without request / git add . / git add -A / stage unrelated files.",
|
|
284
284
|
"",
|
|
285
285
|
"Specialists: explorer (scope unknown), reviewer (verification gap), debugger (verify failure diagnosis), planner (user asks for plan). No deep-reviewer.",
|
|
286
286
|
"",
|
|
@@ -302,10 +302,10 @@ export function buildGPTResolverPrompt() {
|
|
|
302
302
|
"",
|
|
303
303
|
"If piloci MCP available: piloci_recall before inspecting code, piloci_memory after learning something non-obvious.",
|
|
304
304
|
"",
|
|
305
|
-
"Verify: type check or lint MUST pass on changed files. NO EVIDENCE = NOT COMPLETE.",
|
|
305
|
+
"Verify: type check or lint MUST pass on changed files. Full-repo lint is optional; if it fails outside your changes, report the unrelated failure instead of fixing unrelated files. NO EVIDENCE = NOT COMPLETE.",
|
|
306
306
|
"After non-trivial work: ask user to capture lesson → HARNESS.md (infra) or AGENTS.md (agent behavior).",
|
|
307
307
|
"",
|
|
308
|
-
"NEVER: as any / @ts-ignore / leave code broken / delete failing tests / commit without request.",
|
|
308
|
+
"NEVER: as any / @ts-ignore / leave code broken / delete failing tests / commit without request / git add . / git add -A / stage unrelated files.",
|
|
309
309
|
"",
|
|
310
310
|
"Specialists: explorer (scope unknown), reviewer (verification gap), deep-reviewer (risky/security/architectural), debugger (verify failure diagnosis), planner (user asks for plan).",
|
|
311
311
|
"",
|
|
@@ -324,9 +324,9 @@ export function buildCodexResolverPrompt() {
|
|
|
324
324
|
"Trivial fixes → apply yourself. No subagent needed.",
|
|
325
325
|
"3 consecutive failures → STOP, REVERT, REPORT, ASK user.",
|
|
326
326
|
"",
|
|
327
|
-
"Verify: type check or lint MUST pass on changed files. Check LSP diagnostics when available. NO EVIDENCE = NOT COMPLETE.",
|
|
327
|
+
"Verify: type check or lint MUST pass on changed files. Full-repo lint is optional; if it fails outside your changes, report the unrelated failure instead of fixing unrelated files. Check LSP diagnostics when available. NO EVIDENCE = NOT COMPLETE.",
|
|
328
328
|
"",
|
|
329
|
-
"NEVER: as any / @ts-ignore / leave code broken / delete failing tests / commit without request.",
|
|
329
|
+
"NEVER: as any / @ts-ignore / leave code broken / delete failing tests / commit without request / git add . / git add -A / stage unrelated files.",
|
|
330
330
|
"",
|
|
331
331
|
"CONTINUATION: After context compression or any intermediate step, immediately continue the current task. Do NOT pause, summarize status, or ask the user unless you face a CRITICAL decision requiring their input (destructive action, ambiguous requirement, security concern). Keep moving.",
|
|
332
332
|
].join("\n");
|
|
@@ -355,10 +355,10 @@ export function buildResolverPrompt(maxParallelSubagents) {
|
|
|
355
355
|
"",
|
|
356
356
|
"If piloci MCP available: piloci_recall before inspecting code, piloci_memory after learning something non-obvious.",
|
|
357
357
|
"",
|
|
358
|
-
"Verify: type check or lint MUST pass on changed files. Check LSP diagnostics when available. NO EVIDENCE = NOT COMPLETE.",
|
|
358
|
+
"Verify: type check or lint MUST pass on changed files. Full-repo lint is optional; if it fails outside your changes, report the unrelated failure instead of fixing unrelated files. Check LSP diagnostics when available. NO EVIDENCE = NOT COMPLETE.",
|
|
359
359
|
"After non-trivial work: ask user to capture lesson → HARNESS.md (infra) or AGENTS.md (agent behavior).",
|
|
360
360
|
"",
|
|
361
|
-
"NEVER: as any / @ts-ignore / leave code broken / delete failing tests / commit without request.",
|
|
361
|
+
"NEVER: as any / @ts-ignore / leave code broken / delete failing tests / commit without request / git add . / git add -A / stage unrelated files.",
|
|
362
362
|
"",
|
|
363
363
|
"Specialists: explorer (scope unknown), reviewer (verification gap), deep-reviewer (risky/security/architectural), debugger (verify failure diagnosis), planner (user asks for plan).",
|
|
364
364
|
"",
|
package/dist/config.d.ts
CHANGED
|
@@ -27,3 +27,4 @@ export declare const VALID_TOP_LEVEL_KEYS: Set<string>;
|
|
|
27
27
|
export declare const VALID_AGENT_KEYS: Set<string>;
|
|
28
28
|
export declare const VALID_MODES: Set<string>;
|
|
29
29
|
export declare const VALID_PERMISSION_VALUES: Set<string>;
|
|
30
|
+
export declare const VALID_LANGUAGES: Set<string>;
|
package/dist/config.js
CHANGED
|
@@ -138,6 +138,7 @@ export function mergeResolveConfig(...configs) {
|
|
|
138
138
|
result.autoApprove = config.autoApprove ?? result.autoApprove;
|
|
139
139
|
result.maxParallelSubagents = config.maxParallelSubagents ?? result.maxParallelSubagents;
|
|
140
140
|
result.autoUpdate = config.autoUpdate ?? result.autoUpdate;
|
|
141
|
+
result.language = config.language ?? result.language;
|
|
141
142
|
result.models = { ...result.models, ...config.models };
|
|
142
143
|
result.agents = mergeAgents(result.agents, config.agents);
|
|
143
144
|
}
|
|
@@ -244,6 +245,13 @@ export function normalizeResolveConfig(value, source) {
|
|
|
244
245
|
}
|
|
245
246
|
result.tier = tier;
|
|
246
247
|
}
|
|
248
|
+
if (config.language !== undefined) {
|
|
249
|
+
const language = expectString(config.language, `${source}.language`);
|
|
250
|
+
if (!VALID_LANGUAGES.has(language)) {
|
|
251
|
+
throw new Error(`Unknown language "${language}" in ${source}.language. Valid: ${[...VALID_LANGUAGES].join(", ")}`);
|
|
252
|
+
}
|
|
253
|
+
result.language = language;
|
|
254
|
+
}
|
|
247
255
|
if (config.maxParallelSubagents !== undefined) {
|
|
248
256
|
const limit = expectNumber(config.maxParallelSubagents, `${source}.maxParallelSubagents`);
|
|
249
257
|
if (!Number.isInteger(limit) || limit < 1) {
|
|
@@ -387,6 +395,7 @@ export const VALID_TOP_LEVEL_KEYS = new Set([
|
|
|
387
395
|
"autoApprove",
|
|
388
396
|
"maxParallelSubagents",
|
|
389
397
|
"autoUpdate",
|
|
398
|
+
"language",
|
|
390
399
|
"config",
|
|
391
400
|
]);
|
|
392
401
|
export const VALID_AGENT_KEYS = new Set([
|
|
@@ -402,3 +411,4 @@ export const VALID_AGENT_KEYS = new Set([
|
|
|
402
411
|
]);
|
|
403
412
|
export const VALID_MODES = new Set(["subagent", "primary", "all"]);
|
|
404
413
|
export const VALID_PERMISSION_VALUES = new Set(["ask", "allow", "deny"]);
|
|
414
|
+
export const VALID_LANGUAGES = new Set(["auto", "en", "ko"]);
|
package/dist/hooks/index.js
CHANGED
|
@@ -1,12 +1,82 @@
|
|
|
1
1
|
import { DIAGNOSTICS_TTL_MS, FAILURE_PATTERN_TTL_MS, FAILURE_THRESHOLD, STRATEGY_PIVOT_THRESHOLD, EDIT_HOTSPOT_TTL_MS, EDIT_HOTSPOT_THRESHOLD } from "../state.js";
|
|
2
2
|
import { classifyBashCommand, maybeAutoUpdate, detectProjectContext } from "../utils.js";
|
|
3
3
|
import { loadResolveConfig, applyResolveConfig } from "../config.js";
|
|
4
|
+
import { contextMessage, narrate, resolveLocale, t, agentDisplayName, PLUGIN_BRAND } from "../messages.js";
|
|
4
5
|
function capTemperature(current, cap) {
|
|
5
6
|
return typeof current === "number" && Number.isFinite(current)
|
|
6
7
|
? Math.min(current, cap)
|
|
7
8
|
: cap;
|
|
8
9
|
}
|
|
10
|
+
const DISPATCH_KEYS = {
|
|
11
|
+
coder: "dispatch.coder",
|
|
12
|
+
reviewer: "dispatch.reviewer",
|
|
13
|
+
"deep-reviewer": "dispatch.deepReviewer",
|
|
14
|
+
explorer: "dispatch.explorer",
|
|
15
|
+
planner: "dispatch.planner",
|
|
16
|
+
architect: "dispatch.architect",
|
|
17
|
+
researcher: "dispatch.researcher",
|
|
18
|
+
debugger: "dispatch.debugger",
|
|
19
|
+
codex: "dispatch.codex",
|
|
20
|
+
glm: "dispatch.glm",
|
|
21
|
+
gpt: "dispatch.gpt",
|
|
22
|
+
"gpt-coder": "dispatch.gptCoder",
|
|
23
|
+
};
|
|
24
|
+
function extractSubagentType(args) {
|
|
25
|
+
if (!args || typeof args !== "object")
|
|
26
|
+
return undefined;
|
|
27
|
+
const candidate = args.subagent_type ?? args.subagentType ?? args.agent ?? args.type ?? args.role;
|
|
28
|
+
return typeof candidate === "string" && candidate.length > 0 ? candidate : undefined;
|
|
29
|
+
}
|
|
30
|
+
function extractDispatchGoal(args) {
|
|
31
|
+
if (!args || typeof args !== "object")
|
|
32
|
+
return "";
|
|
33
|
+
const candidate = args.description ?? args.title ?? args.task ?? args.prompt;
|
|
34
|
+
if (typeof candidate !== "string")
|
|
35
|
+
return "";
|
|
36
|
+
const trimmed = candidate.trim().split("\n")[0] ?? "";
|
|
37
|
+
return trimmed.length > 80 ? trimmed.slice(0, 77) + "…" : trimmed;
|
|
38
|
+
}
|
|
9
39
|
export function getHooks(directory, options, sessionState) {
|
|
40
|
+
const narrateToolStart = (input, output) => {
|
|
41
|
+
const tool = input.tool;
|
|
42
|
+
if (tool === "task") {
|
|
43
|
+
const sub = extractSubagentType(output?.args ?? input?.args);
|
|
44
|
+
if (!sub)
|
|
45
|
+
return;
|
|
46
|
+
const goal = extractDispatchGoal(output?.args ?? input?.args);
|
|
47
|
+
const to = agentDisplayName(sub, sessionState.locale);
|
|
48
|
+
const key = DISPATCH_KEYS[sub] ?? "dispatch.fromResolver";
|
|
49
|
+
narrate(sessionState, key, { to, goal });
|
|
50
|
+
return;
|
|
51
|
+
}
|
|
52
|
+
if (tool === "edit" || tool === "write") {
|
|
53
|
+
narrate(sessionState, "narration.editing");
|
|
54
|
+
return;
|
|
55
|
+
}
|
|
56
|
+
if (tool === "grep" || tool === "glob") {
|
|
57
|
+
narrate(sessionState, "narration.searching");
|
|
58
|
+
return;
|
|
59
|
+
}
|
|
60
|
+
if (tool === "read") {
|
|
61
|
+
narrate(sessionState, "narration.reading");
|
|
62
|
+
return;
|
|
63
|
+
}
|
|
64
|
+
if (tool === "bash") {
|
|
65
|
+
narrate(sessionState, "narration.bashing");
|
|
66
|
+
return;
|
|
67
|
+
}
|
|
68
|
+
};
|
|
69
|
+
const narrateToolEnd = (input, output) => {
|
|
70
|
+
if (input.tool !== "task")
|
|
71
|
+
return;
|
|
72
|
+
const sub = extractSubagentType(input?.args);
|
|
73
|
+
if (!sub)
|
|
74
|
+
return;
|
|
75
|
+
const to = agentDisplayName(sub, sessionState.locale);
|
|
76
|
+
const errorPresent = Boolean(output?.error) ||
|
|
77
|
+
(typeof output?.metadata === "object" && output.metadata?.error);
|
|
78
|
+
narrate(sessionState, errorPresent ? "dispatch.failed" : "dispatch.completed", { to });
|
|
79
|
+
};
|
|
10
80
|
return {
|
|
11
81
|
event: async (input) => {
|
|
12
82
|
const evt = input.event;
|
|
@@ -134,6 +204,7 @@ export function getHooks(directory, options, sessionState) {
|
|
|
134
204
|
const projectContext = await detectProjectContext(directory);
|
|
135
205
|
sessionState.storedConfig = resolveConfig;
|
|
136
206
|
sessionState.storedProjectContext = projectContext;
|
|
207
|
+
sessionState.locale = resolveLocale(resolveConfig.language, process.env.LANG);
|
|
137
208
|
applyResolveConfig(config, resolveConfig, projectContext);
|
|
138
209
|
if (resolveConfig.autoUpdate !== false && process.env.OPENCODE_RESOLVE_NO_AUTO_UPDATE !== "1") {
|
|
139
210
|
maybeAutoUpdate().catch(() => { });
|
|
@@ -169,6 +240,9 @@ export function getHooks(directory, options, sessionState) {
|
|
|
169
240
|
}
|
|
170
241
|
},
|
|
171
242
|
"chat.params": async (input, output) => {
|
|
243
|
+
if (typeof input.agent === "string" && input.agent.length > 0) {
|
|
244
|
+
sessionState.currentAgent = input.agent;
|
|
245
|
+
}
|
|
172
246
|
const profile = sessionState.storedConfig?.profile;
|
|
173
247
|
if (profile === "glm") {
|
|
174
248
|
output.temperature = capTemperature(output.temperature, 0.4);
|
|
@@ -198,19 +272,23 @@ export function getHooks(directory, options, sessionState) {
|
|
|
198
272
|
}
|
|
199
273
|
},
|
|
200
274
|
"tool.definition": async (input, output) => {
|
|
201
|
-
const
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
275
|
+
const prefix = `[${PLUGIN_BRAND}]`;
|
|
276
|
+
const hintKeys = {
|
|
277
|
+
edit: "tool.edit",
|
|
278
|
+
write: "tool.write",
|
|
279
|
+
bash: "tool.bash",
|
|
280
|
+
task: "tool.task",
|
|
281
|
+
glob: "tool.glob",
|
|
282
|
+
grep: "tool.grep",
|
|
283
|
+
read: "tool.read",
|
|
284
|
+
webfetch: "tool.webfetch",
|
|
285
|
+
todowrite: "tool.todowrite",
|
|
211
286
|
};
|
|
212
|
-
|
|
213
|
-
|
|
287
|
+
const key = hintKeys[input.toolID];
|
|
288
|
+
if (key) {
|
|
289
|
+
// Tool definitions are shipped as tool descriptions on every turn, so
|
|
290
|
+
// keep them English (context-bound) regardless of session locale.
|
|
291
|
+
output.description = output.description + `\n${prefix} ${t(key, "en")}`;
|
|
214
292
|
}
|
|
215
293
|
},
|
|
216
294
|
"command.execute.before": async (_input, output) => {
|
|
@@ -222,7 +300,7 @@ export function getHooks(directory, options, sessionState) {
|
|
|
222
300
|
sessionID: "",
|
|
223
301
|
messageID: "",
|
|
224
302
|
type: "text",
|
|
225
|
-
text:
|
|
303
|
+
text: contextMessage(sessionState.currentAgent, "system.driveResolution"),
|
|
226
304
|
});
|
|
227
305
|
},
|
|
228
306
|
"tool.execute.before": async (input, output) => {
|
|
@@ -242,6 +320,8 @@ export function getHooks(directory, options, sessionState) {
|
|
|
242
320
|
output.args = { ...output.args, _resolve_meta: meta };
|
|
243
321
|
}
|
|
244
322
|
}
|
|
323
|
+
// Role-play narration → terminal only. Does NOT enter LLM context.
|
|
324
|
+
narrateToolStart(input, output);
|
|
245
325
|
},
|
|
246
326
|
"chat.headers": async (input, output) => {
|
|
247
327
|
const providerID = input.provider?.info?.id ?? "";
|
|
@@ -290,6 +370,8 @@ export function getHooks(directory, options, sessionState) {
|
|
|
290
370
|
}
|
|
291
371
|
}
|
|
292
372
|
}
|
|
373
|
+
// Role-play narration → terminal only. Does NOT enter LLM context.
|
|
374
|
+
narrateToolEnd(input, output);
|
|
293
375
|
// For bash: extract key error lines from failing commands
|
|
294
376
|
if (input.tool === "bash") {
|
|
295
377
|
const outputText = typeof output.output === "string" ? output.output
|
|
@@ -308,6 +390,7 @@ export function getHooks(directory, options, sessionState) {
|
|
|
308
390
|
}
|
|
309
391
|
},
|
|
310
392
|
"experimental.session.compacting": async (_input, output) => {
|
|
393
|
+
narrate(sessionState, "narration.compacting");
|
|
311
394
|
const ctx = sessionState.storedProjectContext;
|
|
312
395
|
const cfg = sessionState.storedConfig;
|
|
313
396
|
if (!ctx && !cfg)
|
|
@@ -347,7 +430,7 @@ export function getHooks(directory, options, sessionState) {
|
|
|
347
430
|
contextLines.push(`Session stats: ${sessionState.totalEdits} edits, ${sessionState.totalToolCalls} tool calls.`);
|
|
348
431
|
}
|
|
349
432
|
if (contextLines.length > 0) {
|
|
350
|
-
output.context.push(
|
|
433
|
+
output.context.push(`[${PLUGIN_BRAND}] ` + t("compaction.contextHeader", "en", { body: contextLines.join(" ") }));
|
|
351
434
|
}
|
|
352
435
|
},
|
|
353
436
|
"experimental.chat.messages.transform": async (_input, output) => {
|
|
@@ -407,59 +490,67 @@ export function getHooks(directory, options, sessionState) {
|
|
|
407
490
|
if (!ctx && !hasFailures && !hasLoops)
|
|
408
491
|
return;
|
|
409
492
|
const lines = [];
|
|
493
|
+
const agent = sessionState.currentAgent;
|
|
494
|
+
// All entries below land in LLM context — keep them English.
|
|
410
495
|
if (ctx?.knowledgeFiles.length) {
|
|
411
|
-
lines.push(
|
|
496
|
+
lines.push(contextMessage(agent, "system.projectKnowledge", { files: ctx.knowledgeFiles.join(", ") }));
|
|
412
497
|
}
|
|
413
498
|
if (ctx?.contextFiles.length) {
|
|
414
|
-
lines.push(
|
|
499
|
+
lines.push(contextMessage(agent, "system.contextDocs", { files: ctx.contextFiles.slice(0, 20).join(", ") }));
|
|
415
500
|
}
|
|
416
501
|
if (ctx?.verifyCommands.length) {
|
|
417
|
-
lines.push(
|
|
502
|
+
lines.push(contextMessage(agent, "system.verifyCommands", { commands: ctx.verifyCommands.join("; ") }));
|
|
418
503
|
}
|
|
419
504
|
if (ctx?.hasTypeScript) {
|
|
420
|
-
lines.push("
|
|
505
|
+
lines.push(contextMessage(agent, "system.typescriptMandatory"));
|
|
421
506
|
}
|
|
422
507
|
// Inject failure pattern warnings — encourage trying different approaches, don't stop
|
|
423
508
|
if (hasFailures) {
|
|
424
|
-
lines.push(
|
|
509
|
+
lines.push(contextMessage(agent, "system.failuresHeader"));
|
|
425
510
|
for (const w of sessionState.failureWarnings.slice(0, 3)) {
|
|
426
511
|
lines.push(` - ${w}`);
|
|
427
512
|
}
|
|
428
|
-
lines.push("
|
|
513
|
+
lines.push(t("system.failuresFooter", "en"));
|
|
429
514
|
}
|
|
430
515
|
// Strategy Pivot: after many total failures, suggest architect intervention
|
|
431
516
|
if (sessionState.totalFailures >= STRATEGY_PIVOT_THRESHOLD) {
|
|
432
|
-
lines.push(
|
|
433
|
-
lines.push("
|
|
434
|
-
lines.push("
|
|
517
|
+
lines.push(contextMessage(agent, "system.strategyPivotHeader", { count: sessionState.totalFailures }));
|
|
518
|
+
lines.push(t("system.strategyPivotBody", "en"));
|
|
519
|
+
lines.push(t("system.strategyPivotTail", "en"));
|
|
435
520
|
}
|
|
436
521
|
// Ralph Loop: inject strategy hints when same file edited many times
|
|
437
522
|
if (hasLoops) {
|
|
438
|
-
lines.push(
|
|
523
|
+
lines.push(contextMessage(agent, "system.ralphHeader"));
|
|
439
524
|
for (const w of sessionState.loopWarnings.slice(0, 3)) {
|
|
440
525
|
lines.push(` - ${w}`);
|
|
441
526
|
}
|
|
442
|
-
const
|
|
443
|
-
"
|
|
444
|
-
"
|
|
445
|
-
"
|
|
446
|
-
"
|
|
447
|
-
"
|
|
448
|
-
"
|
|
449
|
-
"
|
|
450
|
-
"
|
|
527
|
+
const strategyKeys = [
|
|
528
|
+
"strategy.rereadFile",
|
|
529
|
+
"strategy.tryDifferent",
|
|
530
|
+
"strategy.useDiagnostics",
|
|
531
|
+
"strategy.smallerPieces",
|
|
532
|
+
"strategy.differentFile",
|
|
533
|
+
"strategy.readTest",
|
|
534
|
+
"strategy.checkImports",
|
|
535
|
+
"strategy.searchSimilar",
|
|
451
536
|
];
|
|
452
|
-
const
|
|
537
|
+
const pickedKey = strategyKeys[Math.floor(Date.now() / 30_000) % strategyKeys.length];
|
|
538
|
+
const hint = t(pickedKey, "en");
|
|
453
539
|
if (hint !== sessionState.lastStrategyHint) {
|
|
454
|
-
lines.push(
|
|
540
|
+
lines.push(`${t("strategy.suggestionLabel", "en")}: ${hint}`);
|
|
455
541
|
sessionState.lastStrategyHint = hint;
|
|
456
542
|
}
|
|
457
|
-
lines.push("
|
|
543
|
+
lines.push(t("system.ralphKeepGoing", "en"));
|
|
458
544
|
}
|
|
459
545
|
// Ralph Loop: inject session context when significant work done
|
|
460
546
|
if (sessionState.totalEdits >= 20 && sessionState.failureWarnings.length > 0) {
|
|
461
|
-
|
|
462
|
-
lines.push("
|
|
547
|
+
const elapsed = Math.round((Date.now() - sessionState.sessionStartTime) / 1000);
|
|
548
|
+
lines.push(contextMessage(agent, "system.sessionStats", {
|
|
549
|
+
edits: sessionState.totalEdits,
|
|
550
|
+
calls: sessionState.totalToolCalls,
|
|
551
|
+
elapsed,
|
|
552
|
+
}));
|
|
553
|
+
lines.push(t("system.iterationWarning", "en"));
|
|
463
554
|
}
|
|
464
555
|
if (lines.length > 0) {
|
|
465
556
|
output.system.push(lines.join("\n"));
|
|
@@ -482,11 +573,11 @@ export function getHooks(directory, options, sessionState) {
|
|
|
482
573
|
const loopSignals = ["trying again", "attempting", "retrying", "second attempt", "third attempt", "another approach", "let me try"];
|
|
483
574
|
const looksLikeLoop = loopSignals.some(s => text.toLowerCase().includes(s));
|
|
484
575
|
if (looksLikeEdit && !alreadyVerified && !isHandoff) {
|
|
485
|
-
output.text = text + "\n\n
|
|
576
|
+
output.text = text + "\n\n" + contextMessage(sessionState.currentAgent, "reminder.verify");
|
|
486
577
|
}
|
|
487
578
|
// Ralph Loop: if loop detected in text AND hotspot exists, suggest strategy change
|
|
488
579
|
if (looksLikeLoop && sessionState.loopWarnings.length > 0) {
|
|
489
|
-
output.text = (output.text ?? text) + "\n\n
|
|
580
|
+
output.text = (output.text ?? text) + "\n\n" + contextMessage(sessionState.currentAgent, "reminder.ralphLoopText");
|
|
490
581
|
}
|
|
491
582
|
}
|
|
492
583
|
};
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
export type Locale = "en" | "ko";
|
|
2
|
+
export declare const PLUGIN_BRAND = "opencode-resolve";
|
|
3
|
+
/** Resolve a Locale from explicit config (`"ko"|"en"|"auto"`) or env. */
|
|
4
|
+
export declare function resolveLocale(configured: string | undefined, envLang: string | undefined): Locale;
|
|
5
|
+
/** Bracketed brand for the currently-active agent. */
|
|
6
|
+
export declare function brand(agent: string | undefined): string;
|
|
7
|
+
/** Friendly display name per agent (used in role-play narration). */
|
|
8
|
+
export declare function agentDisplayName(agent: string | undefined, locale: Locale): string;
|
|
9
|
+
export type MessageKey = "reminder.verify" | "reminder.ralphLoopText" | "system.driveResolution" | "system.projectKnowledge" | "system.contextDocs" | "system.verifyCommands" | "system.typescriptMandatory" | "system.failuresHeader" | "system.failuresFooter" | "system.strategyPivotHeader" | "system.strategyPivotBody" | "system.strategyPivotTail" | "system.ralphHeader" | "system.ralphKeepGoing" | "system.sessionStats" | "system.iterationWarning" | "compaction.contextHeader" | "tool.edit" | "tool.write" | "tool.bash" | "tool.task" | "tool.glob" | "tool.grep" | "tool.read" | "tool.webfetch" | "tool.todowrite" | "dispatch.toSubagent" | "dispatch.fromResolver" | "dispatch.coder" | "dispatch.reviewer" | "dispatch.deepReviewer" | "dispatch.explorer" | "dispatch.planner" | "dispatch.architect" | "dispatch.researcher" | "dispatch.debugger" | "dispatch.codex" | "dispatch.glm" | "dispatch.gpt" | "dispatch.gptCoder" | "dispatch.completed" | "dispatch.failed" | "narration.editing" | "narration.searching" | "narration.reading" | "narration.thinking" | "narration.bashing" | "narration.compacting" | "narration.writing" | "narration.testing" | "narration.typechecking" | "narration.linting" | "narration.git" | "narration.fetch" | "narration.todo" | "narration.diagnostics" | "narration.context" | "narration.verifyPass" | "narration.verifyFail" | "narration.idle" | "strategy.smallerPieces" | "strategy.differentFile" | "strategy.readTest" | "strategy.checkImports" | "strategy.searchSimilar" | "strategy.rereadFile" | "strategy.tryDifferent" | "strategy.useDiagnostics" | "strategy.suggestionLabel";
|
|
10
|
+
type Params = Record<string, string | number>;
|
|
11
|
+
/** Render a message in the requested locale. Picks a random variant if multiple are defined. */
|
|
12
|
+
export declare function t(key: MessageKey, locale: Locale, params?: Params): string;
|
|
13
|
+
/** Compose `[agent] message` for session-time nudges. */
|
|
14
|
+
export declare function brandedMessage(agent: string | undefined, locale: Locale, key: MessageKey, params?: Params): string;
|
|
15
|
+
/** Compose `[opencode-resolve] message` for plugin-level notices. */
|
|
16
|
+
export declare function pluginMessage(locale: Locale, key: MessageKey, params?: Params): string;
|
|
17
|
+
/**
|
|
18
|
+
* Render a context-bound message — always English, regardless of session locale.
|
|
19
|
+
* Use for everything that lands in LLM context (system reminders, tool definitions,
|
|
20
|
+
* end-of-turn reminders that become part of conversation history).
|
|
21
|
+
*/
|
|
22
|
+
export declare function contextMessage(agent: string | undefined, key: MessageKey, params?: Params): string;
|
|
23
|
+
/**
|
|
24
|
+
* Print a terminal-only narration. Uses session locale (Korean if configured).
|
|
25
|
+
* Does NOT enter LLM context — only the user sees it in the OpenCode UI/log.
|
|
26
|
+
* Free to be playful, varied, and bilingual.
|
|
27
|
+
*/
|
|
28
|
+
export declare function narrate(state: {
|
|
29
|
+
locale: Locale;
|
|
30
|
+
currentAgent?: string;
|
|
31
|
+
}, key: MessageKey, params?: Params): void;
|
|
32
|
+
/** All registered message keys, derived from the English table. */
|
|
33
|
+
export declare const ALL_MESSAGE_KEYS: ReadonlyArray<MessageKey>;
|
|
34
|
+
export {};
|