opencode-resolve 0.1.20 → 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/README.ko.md +39 -46
- package/README.md +39 -46
- package/dist/agents.d.ts +3 -19
- package/dist/agents.js +45 -205
- package/dist/checkpoint.d.ts +22 -0
- package/dist/checkpoint.js +72 -0
- package/dist/config.d.ts +3 -1
- package/dist/config.js +30 -47
- package/dist/hooks/index.js +299 -118
- package/dist/index.d.ts +5 -1
- package/dist/index.js +4 -1
- package/dist/messages.d.ts +1 -1
- package/dist/messages.js +28 -56
- package/dist/state.d.ts +13 -0
- package/dist/state.js +9 -0
- package/dist/tools/index.d.ts +11 -2
- package/dist/tools/index.js +131 -21
- package/dist/types.d.ts +12 -5
- package/dist/utils.d.ts +11 -11
- package/dist/utils.js +55 -97
- package/opencode-resolve.example.json +0 -15
- package/opencode-resolve.reference.jsonc +94 -130
- package/package.json +1 -1
- package/scripts/cli.mjs +13 -9
- package/scripts/install-local.mjs +4 -18
- package/scripts/postinstall.mjs +132 -495
package/dist/agents.js
CHANGED
|
@@ -4,11 +4,7 @@ export const VALID_AGENT_NAMES = [
|
|
|
4
4
|
"coder",
|
|
5
5
|
"reviewer",
|
|
6
6
|
"resolver",
|
|
7
|
-
"codex",
|
|
8
|
-
"gpt",
|
|
9
|
-
"glm",
|
|
10
7
|
"architect",
|
|
11
|
-
"gpt-coder",
|
|
12
8
|
"debugger",
|
|
13
9
|
"researcher",
|
|
14
10
|
"explorer",
|
|
@@ -20,7 +16,7 @@ export const DEFAULT_AGENT_CONFIG = {
|
|
|
20
16
|
coder: {
|
|
21
17
|
mode: "subagent",
|
|
22
18
|
color: "#7CFC00",
|
|
23
|
-
maxSteps:
|
|
19
|
+
maxSteps: 15,
|
|
24
20
|
description: "Use for focused implementation, file edits, test runs, and fixing issues until the task is resolved.",
|
|
25
21
|
prompt: [
|
|
26
22
|
"You are Coder, a focused implementation subagent for OpenCode Resolve.",
|
|
@@ -45,7 +41,7 @@ export const DEFAULT_AGENT_CONFIG = {
|
|
|
45
41
|
reviewer: {
|
|
46
42
|
mode: "subagent",
|
|
47
43
|
color: "#8A7CFF",
|
|
48
|
-
maxSteps:
|
|
44
|
+
maxSteps: 6,
|
|
49
45
|
description: "Internal read-only verification-gap auditor. Enabled as subagent by default but not part of the core resolver→coder path. Resolver dispatches only when it judges a verification gap exists on non-trivial changes.",
|
|
50
46
|
prompt: [
|
|
51
47
|
"You are Reviewer, a strictly read-only internal review subagent for OpenCode Resolve.",
|
|
@@ -65,7 +61,7 @@ export const DEFAULT_AGENT_CONFIG = {
|
|
|
65
61
|
resolver: {
|
|
66
62
|
mode: "all",
|
|
67
63
|
color: "#FF7AC6",
|
|
68
|
-
maxSteps:
|
|
64
|
+
maxSteps: 25,
|
|
69
65
|
description: "Primary orchestrator in the fixed-role verified loop (resolver→coder). Decomposes work into verified checkpoints, dispatches coder, verifies each, and carries forward progress. Internal subagents (explorer, reviewer, deep-reviewer) are available by default but dispatched only when justified.",
|
|
70
66
|
prompt: buildResolverPrompt(undefined),
|
|
71
67
|
permission: {
|
|
@@ -74,30 +70,6 @@ export const DEFAULT_AGENT_CONFIG = {
|
|
|
74
70
|
webfetch: "allow",
|
|
75
71
|
},
|
|
76
72
|
},
|
|
77
|
-
codex: {
|
|
78
|
-
mode: "all",
|
|
79
|
-
color: "#FFB347",
|
|
80
|
-
maxSteps: 35,
|
|
81
|
-
description: "Codex-optimized primary resolver for agentic coding work. Use when a Codex-style OpenAI coding model is pinned and you want a dedicated user-facing route separate from the neutral resolver.",
|
|
82
|
-
prompt: buildCodexResolverPrompt(),
|
|
83
|
-
permission: {
|
|
84
|
-
edit: "allow",
|
|
85
|
-
bash: "ask",
|
|
86
|
-
webfetch: "allow",
|
|
87
|
-
},
|
|
88
|
-
},
|
|
89
|
-
gpt: {
|
|
90
|
-
mode: "all",
|
|
91
|
-
color: "#FFB347",
|
|
92
|
-
maxSteps: 35,
|
|
93
|
-
description: "GPT-optimized primary resolver for agentic coding work. Use when a GPT/OpenAI coding model is pinned and you want a dedicated user-facing route separate from the neutral resolver.",
|
|
94
|
-
prompt: buildGPTResolverPrompt(),
|
|
95
|
-
permission: {
|
|
96
|
-
edit: "allow",
|
|
97
|
-
bash: "ask",
|
|
98
|
-
webfetch: "allow",
|
|
99
|
-
},
|
|
100
|
-
},
|
|
101
73
|
architect: {
|
|
102
74
|
mode: "subagent",
|
|
103
75
|
color: "#00BFFF",
|
|
@@ -114,22 +86,6 @@ export const DEFAULT_AGENT_CONFIG = {
|
|
|
114
86
|
webfetch: "allow",
|
|
115
87
|
},
|
|
116
88
|
},
|
|
117
|
-
"gpt-coder": {
|
|
118
|
-
mode: "subagent",
|
|
119
|
-
color: "#FFB347",
|
|
120
|
-
maxSteps: 20,
|
|
121
|
-
description: "Use for difficult implementation work that needs stronger reasoning than the default coder.",
|
|
122
|
-
prompt: [
|
|
123
|
-
"You are GPT Coder, a high-reasoning implementation subagent for difficult tasks.",
|
|
124
|
-
"Use the same small-change discipline as Coder, but take extra care with design, edge cases, and verification.",
|
|
125
|
-
"Inspect before editing, implement directly, verify when practical, and report exactly what changed.",
|
|
126
|
-
].join("\n"),
|
|
127
|
-
permission: {
|
|
128
|
-
edit: "allow",
|
|
129
|
-
bash: "ask",
|
|
130
|
-
webfetch: "allow",
|
|
131
|
-
},
|
|
132
|
-
},
|
|
133
89
|
debugger: {
|
|
134
90
|
mode: "subagent",
|
|
135
91
|
color: "#FF5F57",
|
|
@@ -165,7 +121,7 @@ export const DEFAULT_AGENT_CONFIG = {
|
|
|
165
121
|
explorer: {
|
|
166
122
|
mode: "subagent",
|
|
167
123
|
color: "#33CCFF",
|
|
168
|
-
maxSteps:
|
|
124
|
+
maxSteps: 5,
|
|
169
125
|
description: "Internal pre-change fast scout for codebase/file/pattern/doc discovery. Enabled as subagent by default but not part of the core path. Read-only; quick model.",
|
|
170
126
|
prompt: [
|
|
171
127
|
"You are Explorer, a fast codebase scout subagent for OpenCode Resolve.",
|
|
@@ -203,7 +159,7 @@ export const DEFAULT_AGENT_CONFIG = {
|
|
|
203
159
|
planner: {
|
|
204
160
|
mode: "subagent",
|
|
205
161
|
color: "#F4A300",
|
|
206
|
-
maxSteps:
|
|
162
|
+
maxSteps: 8,
|
|
207
163
|
description: "Internal advanced planner dispatched by the resolver when the user explicitly asks for a plan, decomposition, or implementation strategy. Read-only. Returns a concrete plan; never edits code.",
|
|
208
164
|
prompt: [
|
|
209
165
|
"You are Planner, the advanced planning subagent for OpenCode Resolve.",
|
|
@@ -219,139 +175,51 @@ export const DEFAULT_AGENT_CONFIG = {
|
|
|
219
175
|
webfetch: "allow",
|
|
220
176
|
},
|
|
221
177
|
},
|
|
222
|
-
glm: {
|
|
223
|
-
mode: "all",
|
|
224
|
-
color: "#00FF9F",
|
|
225
|
-
maxSteps: 30,
|
|
226
|
-
description: "GLM-optimized orchestrator for ZAI coding-plan. Select this agent when running GLM-only to get maximum performance within session and rate limits. Serial coder dispatch, token-efficient prompts, coding-plan constraints handled automatically.",
|
|
227
|
-
prompt: buildGLMResolverPrompt(undefined),
|
|
228
|
-
permission: {
|
|
229
|
-
edit: "allow",
|
|
230
|
-
bash: "ask",
|
|
231
|
-
webfetch: "allow",
|
|
232
|
-
},
|
|
233
|
-
},
|
|
234
178
|
};
|
|
235
|
-
export
|
|
236
|
-
"You are Coder (GLM profile), a concise implementation subagent for OpenCode Resolve.",
|
|
237
|
-
"",
|
|
238
|
-
"Read ONLY files you will edit. Make the SMALLEST correct change.",
|
|
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
|
-
"Return: changed files + verification result. No prose.",
|
|
241
|
-
"",
|
|
242
|
-
"NO EVIDENCE = INCOMPLETE WORK.",
|
|
243
|
-
"",
|
|
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
|
-
].join("\n");
|
|
246
|
-
export const GPT_CODER_PROMPT = [
|
|
247
|
-
"You are Coder (GPT profile), an implementation subagent for OpenCode Resolve.",
|
|
248
|
-
"",
|
|
249
|
-
"Read ONLY files you need. Make the SMALLEST correct change.",
|
|
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
|
-
"Return: changed files + verification result. Keep it concise.",
|
|
252
|
-
"",
|
|
253
|
-
"NO EVIDENCE = INCOMPLETE WORK.",
|
|
254
|
-
"",
|
|
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
|
-
].join("\n");
|
|
257
|
-
export function buildGLMResolverPrompt(maxParallelSubagents) {
|
|
179
|
+
export function buildResolverPrompt(maxParallelSubagents, singleAgentMode = false) {
|
|
258
180
|
const limit = typeof maxParallelSubagents === "number" && Number.isFinite(maxParallelSubagents)
|
|
259
181
|
? Math.max(1, Math.trunc(maxParallelSubagents))
|
|
260
|
-
:
|
|
261
|
-
const parallelRule = limit
|
|
262
|
-
? "
|
|
263
|
-
: limit
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
"
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
"You are Resolver (GPT profile), the orchestrator for OpenCode Resolve.",
|
|
293
|
-
"Leverage GPT's reasoning — parallel dispatch, detailed checkpoint plans for deep tasks.",
|
|
294
|
-
"",
|
|
295
|
-
"Parallel coder dispatch for independent work. Deep-reviewer available for risky changes.",
|
|
296
|
-
"Dispatch coder with: TASK (atomic goal), OUTCOME (success criteria), MUST DO, MUST NOT DO, CONTEXT (files/patterns).",
|
|
297
|
-
"After EVERY coder return: verify it works + follows codebase patterns. If not → re-dispatch with fix.",
|
|
298
|
-
"INTELLIGENT RECOVERY: On verify failure, dispatch debugger FIRST to diagnose root cause, THEN re-dispatch coder with precise fix. Do NOT blindly retry.",
|
|
299
|
-
"Trivial fixes → apply yourself. No subagent needed.",
|
|
300
|
-
"3 consecutive failures → STOP, REVERT, REPORT, ASK user.",
|
|
301
|
-
"10+ failures on same task → call architect to rethink the approach before continuing.",
|
|
302
|
-
"",
|
|
303
|
-
"If piloci MCP available: piloci_recall before inspecting code, piloci_memory after learning something non-obvious.",
|
|
304
|
-
"",
|
|
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
|
-
"After non-trivial work: ask user to capture lesson → HARNESS.md (infra) or AGENTS.md (agent behavior).",
|
|
307
|
-
"",
|
|
308
|
-
"NEVER: as any / @ts-ignore / leave code broken / delete failing tests / commit without request / git add . / git add -A / stage unrelated files.",
|
|
309
|
-
"",
|
|
310
|
-
"Specialists: explorer (scope unknown), reviewer (verification gap), deep-reviewer (risky/security/architectural), debugger (verify failure diagnosis), planner (user asks for plan).",
|
|
311
|
-
"",
|
|
312
|
-
"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.",
|
|
313
|
-
].join("\n");
|
|
314
|
-
}
|
|
315
|
-
export function buildCodexResolverPrompt() {
|
|
316
|
-
return [
|
|
317
|
-
"You are Codex Resolver, the OpenAI Codex-optimized primary agent for OpenCode Resolve.",
|
|
318
|
-
"Use Codex-style software engineering judgment: inspect narrowly, patch directly when trivial, dispatch focused coders when parallel work is useful, and verify before reporting.",
|
|
319
|
-
"",
|
|
320
|
-
"Prefer coder for implementation slices, explorer for quick scope discovery, reviewer for verification gaps, deep-reviewer for risky/security/architecture changes, debugger for verify failure diagnosis, planner only when the user asks for a plan.",
|
|
321
|
-
"Dispatch coder with: TASK (atomic goal), OUTCOME (success criteria), MUST DO, MUST NOT DO, CONTEXT (files/patterns).",
|
|
322
|
-
"After EVERY coder return: verify it works + follows codebase patterns. If not → re-dispatch with precise fix.",
|
|
323
|
-
"INTELLIGENT RECOVERY: On verify failure, dispatch debugger FIRST to diagnose root cause, THEN re-dispatch coder with precise fix. Do NOT blindly retry.",
|
|
324
|
-
"Trivial fixes → apply yourself. No subagent needed.",
|
|
325
|
-
"3 consecutive failures → STOP, REVERT, REPORT, ASK user.",
|
|
326
|
-
"",
|
|
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
|
-
"",
|
|
329
|
-
"NEVER: as any / @ts-ignore / leave code broken / delete failing tests / commit without request / git add . / git add -A / stage unrelated files.",
|
|
330
|
-
"",
|
|
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
|
-
].join("\n");
|
|
333
|
-
}
|
|
334
|
-
export function buildResolverPrompt(maxParallelSubagents) {
|
|
335
|
-
const explicitLimit = typeof maxParallelSubagents === "number" && Number.isFinite(maxParallelSubagents)
|
|
336
|
-
? Math.max(1, Math.trunc(maxParallelSubagents))
|
|
337
|
-
: undefined;
|
|
338
|
-
const parallelRule = explicitLimit === undefined
|
|
339
|
-
? "Fan out for independent work. Back off on rate-limit errors."
|
|
340
|
-
: explicitLimit === 1
|
|
341
|
-
? "Dispatch ONE coder at a time. Wait for it to finish."
|
|
342
|
-
: `Dispatch up to ${explicitLimit} coders concurrently.`;
|
|
182
|
+
: 1;
|
|
183
|
+
const parallelRule = limit <= 1
|
|
184
|
+
? "Serial dispatch: ONE coder at a time. Wait for verification before the next dispatch."
|
|
185
|
+
: `Dispatch up to ${limit} coders concurrently, then WAIT for all to verify before continuing.`;
|
|
186
|
+
const dispatchRule = `Parallel: ${parallelRule}`;
|
|
187
|
+
// singleAgentMode: resolver edits directly — no coder dispatch. Cheaper for small tasks,
|
|
188
|
+
// avoids subagent context duplication. The verify loop discipline still applies.
|
|
189
|
+
const modeRule = singleAgentMode
|
|
190
|
+
? [
|
|
191
|
+
"DIRECT MODE (single-agent): Make ALL edits yourself with edit/write tools. Do NOT dispatch a coder subagent for implementation.",
|
|
192
|
+
"Use subagents (explorer/reviewer/debugger) only to GATHER information or diagnose — never to write code.",
|
|
193
|
+
].join("\n")
|
|
194
|
+
: dispatchRule;
|
|
195
|
+
const loopSteps = singleAgentMode
|
|
196
|
+
? [
|
|
197
|
+
"LOOP DISCIPLINE (mandatory):",
|
|
198
|
+
" 1. make the smallest correct edit yourself (read the file first, change the least that fixes it)",
|
|
199
|
+
" 2. run the verify command (type check / lint / test) — NO EXCEPTION",
|
|
200
|
+
" 3. on verify FAIL → read the exact error, fix the precise cause. Do NOT repeat the same change.",
|
|
201
|
+
" 4. loop steps 1-3 until the change is verified. Only THEN report done.",
|
|
202
|
+
].join("\n")
|
|
203
|
+
: [
|
|
204
|
+
"LOOP DISCIPLINE (mandatory):",
|
|
205
|
+
" 1. dispatch coder with TASK / OUTCOME / MUST DO / MUST NOT DO / CONTEXT",
|
|
206
|
+
" 2. after EVERY coder return, run the verify command (type check / lint / test) — NO EXCEPTION",
|
|
207
|
+
" 3. on verify FAIL → re-dispatch coder with the exact error + a precise fix instruction. Do NOT repeat the same change.",
|
|
208
|
+
" 4. loop steps 1-3 until the change is verified. Only THEN report done.",
|
|
209
|
+
" 5. trivial fix → apply it yourself (no subagent), then verify.",
|
|
210
|
+
].join("\n");
|
|
211
|
+
const recovery = singleAgentMode
|
|
212
|
+
? "INTELLIGENT RECOVERY: On verify failure, run resolve-diagnostics and read the error closely before re-editing. Never blindly retry the identical change."
|
|
213
|
+
: "INTELLIGENT RECOVERY: On verify failure, dispatch debugger FIRST to diagnose root cause, THEN re-dispatch coder with the precise fix. Never blindly retry the identical change.";
|
|
343
214
|
return [
|
|
344
215
|
"You are Resolver, the context-efficient orchestrator for OpenCode Resolve.",
|
|
345
|
-
"
|
|
346
|
-
"
|
|
216
|
+
"Your single job: drive the task to a VERIFIED resolution and keep the resolve loop closed until it converges.",
|
|
217
|
+
"Token budget is finite. Minimize unnecessary reads; one focused change beats several exploratory ones.",
|
|
347
218
|
"",
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
"
|
|
352
|
-
"Trivial fixes → apply yourself. No subagent needed.",
|
|
353
|
-
"3 consecutive failures → STOP, REVERT, REPORT, ASK user.",
|
|
354
|
-
"10+ failures on same task → call architect to rethink the approach before continuing.",
|
|
219
|
+
modeRule,
|
|
220
|
+
loopSteps,
|
|
221
|
+
recovery,
|
|
222
|
+
"ESCALATION (enforced by the harness, follow it): 3 consecutive failures → STOP, revert the last change, report to the user. 6 → pivot to architect for a different approach.",
|
|
355
223
|
"",
|
|
356
224
|
"If piloci MCP available: piloci_recall before inspecting code, piloci_memory after learning something non-obvious.",
|
|
357
225
|
"",
|
|
@@ -367,47 +235,19 @@ export function buildResolverPrompt(maxParallelSubagents) {
|
|
|
367
235
|
}
|
|
368
236
|
export const VALID_MODEL_ALIASES = [
|
|
369
237
|
...VALID_AGENT_NAMES,
|
|
370
|
-
"glm",
|
|
371
|
-
"gpt",
|
|
372
238
|
"quick",
|
|
373
239
|
"deep",
|
|
374
240
|
"fast",
|
|
375
241
|
"strong",
|
|
376
242
|
"mini",
|
|
377
|
-
"codex",
|
|
378
243
|
"bronze",
|
|
379
244
|
"silver",
|
|
380
245
|
"gold",
|
|
381
|
-
"gpt-bronze",
|
|
382
|
-
"gpt-silver",
|
|
383
|
-
"gpt-gold",
|
|
384
|
-
"glm-bronze",
|
|
385
|
-
"glm-silver",
|
|
386
|
-
"glm-gold",
|
|
387
246
|
];
|
|
388
247
|
export const VALID_MODEL_ALIAS_SET = new Set(VALID_MODEL_ALIASES);
|
|
389
|
-
export const VALID_PROFILES = new Set(["mix", "glm", "gpt"]);
|
|
390
248
|
export const VALID_TIERS = new Set(["bronze", "silver", "gold"]);
|
|
391
|
-
export const GLM_ENABLED = ["coder", "resolver", "glm", "explorer", "reviewer", "planner"];
|
|
392
|
-
export const GPT_ENABLED = ["coder", "resolver", "gpt", "explorer", "reviewer", "deep-reviewer", "planner"];
|
|
393
249
|
export const TIER_ENABLED = {
|
|
394
250
|
bronze: ["coder", "resolver"],
|
|
395
251
|
silver: ["coder", "resolver", "explorer", "reviewer", "planner"],
|
|
396
|
-
gold: ["coder", "resolver", "
|
|
397
|
-
};
|
|
398
|
-
export const GLM_AGENT_OVERRIDES = {
|
|
399
|
-
coder: { maxSteps: 15 },
|
|
400
|
-
resolver: { maxSteps: 25 },
|
|
401
|
-
explorer: { maxSteps: 5 },
|
|
402
|
-
reviewer: { maxSteps: 6 },
|
|
403
|
-
planner: { maxSteps: 8 },
|
|
404
|
-
};
|
|
405
|
-
export const GPT_AGENT_OVERRIDES = {
|
|
406
|
-
coder: { maxSteps: 25 },
|
|
407
|
-
resolver: { maxSteps: 40 },
|
|
408
|
-
gpt: { maxSteps: 40 },
|
|
409
|
-
explorer: { maxSteps: 8 },
|
|
410
|
-
reviewer: { maxSteps: 10 },
|
|
411
|
-
"deep-reviewer": { maxSteps: 15 },
|
|
412
|
-
planner: { maxSteps: 12 },
|
|
252
|
+
gold: ["coder", "resolver", "explorer", "reviewer", "deep-reviewer", "planner", "architect", "debugger", "researcher"],
|
|
413
253
|
};
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import type { RollbackKind } from "./types.js";
|
|
2
|
+
/** Namespace for checkpoint refs. Outside refs/heads, so it never shows up as a branch. */
|
|
3
|
+
export declare const CHECKPOINT_REF_NAMESPACE = "refs/resolve-checkpoint";
|
|
4
|
+
export type Checkpoint = {
|
|
5
|
+
/** Full ref name, e.g. `refs/resolve-checkpoint/1730000000000-reset`. */
|
|
6
|
+
ref: string;
|
|
7
|
+
/** Copy-pasteable command that puts the worktree back the way it was. */
|
|
8
|
+
restoreCommand: string;
|
|
9
|
+
};
|
|
10
|
+
export declare function isGitRepository(directory: string): Promise<boolean>;
|
|
11
|
+
/**
|
|
12
|
+
* Snapshot the worktree into `refs/resolve-checkpoint/<timestamp>-<kind>`.
|
|
13
|
+
* Throws with the underlying git error if the snapshot cannot be written — the
|
|
14
|
+
* caller must abort the destructive command rather than run it unprotected.
|
|
15
|
+
*/
|
|
16
|
+
export declare function createRollbackCheckpoint(directory: string, kind: RollbackKind): Promise<Checkpoint>;
|
|
17
|
+
/**
|
|
18
|
+
* Restores every path recorded in the checkpoint, including files a later
|
|
19
|
+
* `git clean` deleted. Files created after the checkpoint are left alone —
|
|
20
|
+
* recovery is additive, never destructive.
|
|
21
|
+
*/
|
|
22
|
+
export declare function restoreCommandFor(ref: string): string;
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
// Rollback checkpoints.
|
|
2
|
+
//
|
|
3
|
+
// `git reset --hard` and `git clean -f` are the two recovery commands an agent
|
|
4
|
+
// needs to escape a tangled worktree, and they are also the two that can destroy
|
|
5
|
+
// uncommitted work. Before either runs, we snapshot the entire worktree —
|
|
6
|
+
// tracked modifications *and* untracked files — into a git ref that nothing else
|
|
7
|
+
// points at.
|
|
8
|
+
//
|
|
9
|
+
// The snapshot uses a throwaway index (`GIT_INDEX_FILE`) so the real index and
|
|
10
|
+
// the working tree are never touched: add -A → write-tree → commit-tree →
|
|
11
|
+
// update-ref. Nothing is staged, nothing is committed to any branch, no HEAD
|
|
12
|
+
// move. The ref keeps the objects alive against `git gc`.
|
|
13
|
+
import { mkdtemp, rm } from "node:fs/promises";
|
|
14
|
+
import { tmpdir } from "node:os";
|
|
15
|
+
import { join } from "node:path";
|
|
16
|
+
import { runCommand } from "./utils.js";
|
|
17
|
+
/** Namespace for checkpoint refs. Outside refs/heads, so it never shows up as a branch. */
|
|
18
|
+
export const CHECKPOINT_REF_NAMESPACE = "refs/resolve-checkpoint";
|
|
19
|
+
const GIT_TIMEOUT_MS = 15_000;
|
|
20
|
+
export async function isGitRepository(directory) {
|
|
21
|
+
const { exitCode } = await runCommand("git rev-parse --is-inside-work-tree", directory, GIT_TIMEOUT_MS);
|
|
22
|
+
return exitCode === 0;
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Snapshot the worktree into `refs/resolve-checkpoint/<timestamp>-<kind>`.
|
|
26
|
+
* Throws with the underlying git error if the snapshot cannot be written — the
|
|
27
|
+
* caller must abort the destructive command rather than run it unprotected.
|
|
28
|
+
*/
|
|
29
|
+
export async function createRollbackCheckpoint(directory, kind) {
|
|
30
|
+
const ref = `${CHECKPOINT_REF_NAMESPACE}/${Date.now()}-${kind}`;
|
|
31
|
+
const indexDirectory = await mkdtemp(join(tmpdir(), "opencode-resolve-ckpt-"));
|
|
32
|
+
const indexFile = join(indexDirectory, "index");
|
|
33
|
+
// One shell invocation: a failure anywhere aborts (set -e) and we surface it.
|
|
34
|
+
// `git add -A` against an empty throwaway index stages the whole worktree,
|
|
35
|
+
// honouring .gitignore. `commit-tree` parents on HEAD when a HEAD exists (a
|
|
36
|
+
// freshly-initialised repo has none).
|
|
37
|
+
const script = [
|
|
38
|
+
"set -e",
|
|
39
|
+
`export GIT_INDEX_FILE=${JSON.stringify(indexFile)}`,
|
|
40
|
+
// commit-tree refuses to run without a committer identity; a repo with no
|
|
41
|
+
// user.email configured must still be able to checkpoint.
|
|
42
|
+
"export GIT_AUTHOR_NAME=opencode-resolve GIT_AUTHOR_EMAIL=checkpoint@opencode-resolve.local",
|
|
43
|
+
"export GIT_COMMITTER_NAME=opencode-resolve GIT_COMMITTER_EMAIL=checkpoint@opencode-resolve.local",
|
|
44
|
+
"git add -A",
|
|
45
|
+
"tree=$(git write-tree)",
|
|
46
|
+
`message=${JSON.stringify(`opencode-resolve checkpoint before git ${kind}`)}`,
|
|
47
|
+
'if git rev-parse -q --verify HEAD >/dev/null 2>&1; then',
|
|
48
|
+
' commit=$(git commit-tree "$tree" -p HEAD -m "$message")',
|
|
49
|
+
"else",
|
|
50
|
+
' commit=$(git commit-tree "$tree" -m "$message")',
|
|
51
|
+
"fi",
|
|
52
|
+
`git update-ref ${JSON.stringify(ref)} "$commit"`,
|
|
53
|
+
].join("\n");
|
|
54
|
+
try {
|
|
55
|
+
const { stderr, exitCode } = await runCommand(script, directory, GIT_TIMEOUT_MS);
|
|
56
|
+
if (exitCode !== 0) {
|
|
57
|
+
throw new Error(`checkpoint failed (git exit ${exitCode}): ${stderr.trim() || "no stderr"}`);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
finally {
|
|
61
|
+
await rm(indexDirectory, { recursive: true, force: true }).catch(() => { });
|
|
62
|
+
}
|
|
63
|
+
return { ref, restoreCommand: restoreCommandFor(ref) };
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* Restores every path recorded in the checkpoint, including files a later
|
|
67
|
+
* `git clean` deleted. Files created after the checkpoint are left alone —
|
|
68
|
+
* recovery is additive, never destructive.
|
|
69
|
+
*/
|
|
70
|
+
export function restoreCommandFor(ref) {
|
|
71
|
+
return `git restore --source=${ref} -- .`;
|
|
72
|
+
}
|
package/dist/config.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { Config } from "@opencode-ai/plugin";
|
|
2
|
-
import { ResolveConfig, ProjectContext, ResolveAgentName, UnknownRecord, ResolvePluginOptions, ResolveAgentConfig, PermissionValue } from "./types.js";
|
|
2
|
+
import { ResolveConfig, ProjectContext, ResolveAgentName, UnknownRecord, ResolvePluginOptions, ResolveAgentConfig, PermissionValue, ResolvePermissions } from "./types.js";
|
|
3
3
|
export declare function applyResolveConfig(config: Config, resolveConfig: ResolveConfig, projectContext: ProjectContext): void;
|
|
4
4
|
export declare function buildContextInjection(ctx: ProjectContext): string;
|
|
5
5
|
export declare function defaultResolveConfig(): ResolveConfig;
|
|
@@ -11,6 +11,7 @@ export declare function getPluginOptions(config: Config): unknown;
|
|
|
11
11
|
export declare function isResolvePluginEntry(entry: string): boolean;
|
|
12
12
|
export declare function resolvePath(path: string, directory: string): string;
|
|
13
13
|
export declare function normalizeResolveConfig(value: unknown, source: string): ResolvePluginOptions;
|
|
14
|
+
export declare function normalizePermissions(value: unknown, source: string): ResolvePermissions;
|
|
14
15
|
export declare function normalizeAgentConfig(value: unknown, source: string): ResolveAgentConfig;
|
|
15
16
|
export declare function normalizeTools(value: unknown, source: string): Record<string, boolean>;
|
|
16
17
|
export declare function normalizePermission(value: unknown, source: string): ResolveAgentConfig["permission"];
|
|
@@ -24,6 +25,7 @@ export declare function expectNumber(value: unknown, source: string): number;
|
|
|
24
25
|
export declare function isObject(value: unknown): value is UnknownRecord;
|
|
25
26
|
export declare function loadResolveConfig(directory: string, opencodeConfig: Config, options: unknown): Promise<ResolveConfig>;
|
|
26
27
|
export declare const VALID_TOP_LEVEL_KEYS: Set<string>;
|
|
28
|
+
export declare const VALID_PERMISSIONS_KEYS: Set<string>;
|
|
27
29
|
export declare const VALID_AGENT_KEYS: Set<string>;
|
|
28
30
|
export declare const VALID_MODES: Set<string>;
|
|
29
31
|
export declare const VALID_PERMISSION_VALUES: Set<string>;
|
package/dist/config.js
CHANGED
|
@@ -1,17 +1,14 @@
|
|
|
1
1
|
import { join, basename, isAbsolute, resolve } from "node:path";
|
|
2
2
|
import { homedir } from "node:os";
|
|
3
|
-
import { DEFAULT_AGENT_CONFIG,
|
|
3
|
+
import { DEFAULT_AGENT_CONFIG, buildResolverPrompt, VALID_AGENT_NAME_SET, DEFAULT_MODELS, DEFAULT_ENABLED, VALID_AGENT_NAMES, TIER_ENABLED, VALID_MODEL_ALIAS_SET, VALID_TIERS } from "./agents.js";
|
|
4
4
|
import { readFirstJson } from "./utils.js";
|
|
5
5
|
export function applyResolveConfig(config, resolveConfig, projectContext) {
|
|
6
|
-
const profile = resolveConfig.profile;
|
|
7
|
-
const isGLM = profile === "glm";
|
|
8
|
-
const isGPT = profile === "gpt";
|
|
9
|
-
const profileEnabled = isGLM ? GLM_ENABLED : isGPT ? GPT_ENABLED : undefined;
|
|
10
6
|
const tierEnabled = resolveConfig.tier ? TIER_ENABLED[resolveConfig.tier] : undefined;
|
|
11
|
-
const enabled = new Set(resolveConfig.enabled ?? tierEnabled ??
|
|
7
|
+
const enabled = new Set(resolveConfig.enabled ?? tierEnabled ?? DEFAULT_ENABLED);
|
|
12
8
|
const models = { ...DEFAULT_MODELS, ...resolveConfig.models };
|
|
13
9
|
const defaultModel = typeof config.model === "string" ? config.model : undefined;
|
|
14
10
|
const maxParallelSubagents = resolveConfig.maxParallelSubagents;
|
|
11
|
+
const singleAgentMode = resolveConfig.singleAgentMode === true;
|
|
15
12
|
const contextInjection = buildContextInjection(projectContext);
|
|
16
13
|
config.agent ??= {};
|
|
17
14
|
for (const name of Object.keys(DEFAULT_AGENT_CONFIG)) {
|
|
@@ -20,34 +17,18 @@ export function applyResolveConfig(config, resolveConfig, projectContext) {
|
|
|
20
17
|
if (!isEnabled)
|
|
21
18
|
continue;
|
|
22
19
|
const base = DEFAULT_AGENT_CONFIG[name];
|
|
23
|
-
const profileOverride = isGLM ? GLM_AGENT_OVERRIDES[name] : isGPT ? GPT_AGENT_OVERRIDES[name] : undefined;
|
|
24
20
|
const { enabled: _enabled, model: requestedModel, permission: userPermission, ...agentOverride } = override ?? {};
|
|
25
21
|
const model = resolveModel(requestedModel ?? models[name] ?? defaultModel, models);
|
|
26
22
|
const permission = buildPermission(base.permission, userPermission);
|
|
27
23
|
const agentConfig = {
|
|
28
24
|
...base,
|
|
29
|
-
...profileOverride,
|
|
30
25
|
...agentOverride,
|
|
31
26
|
};
|
|
32
27
|
if (agentOverride.prompt === undefined) {
|
|
33
|
-
if (
|
|
34
|
-
|
|
35
|
-
agentConfig.prompt = buildGLMResolverPrompt(maxParallelSubagents);
|
|
36
|
-
else if (name === "coder")
|
|
37
|
-
agentConfig.prompt = GLM_CODER_PROMPT;
|
|
28
|
+
if (name === "resolver") {
|
|
29
|
+
agentConfig.prompt = buildResolverPrompt(maxParallelSubagents, singleAgentMode);
|
|
38
30
|
}
|
|
39
|
-
|
|
40
|
-
if (name === "resolver")
|
|
41
|
-
agentConfig.prompt = buildGPTResolverPrompt();
|
|
42
|
-
else if (name === "coder")
|
|
43
|
-
agentConfig.prompt = GPT_CODER_PROMPT;
|
|
44
|
-
}
|
|
45
|
-
else {
|
|
46
|
-
if (name === "resolver")
|
|
47
|
-
agentConfig.prompt = buildResolverPrompt(maxParallelSubagents);
|
|
48
|
-
}
|
|
49
|
-
// Inject project context into all resolver-type agents
|
|
50
|
-
if ((name === "resolver" || name === "codex" || name === "glm") && contextInjection) {
|
|
31
|
+
if (name === "resolver" && contextInjection) {
|
|
51
32
|
agentConfig.prompt = agentConfig.prompt + "\n\n" + contextInjection;
|
|
52
33
|
}
|
|
53
34
|
// Inject verify commands into coder prompts
|
|
@@ -61,13 +42,6 @@ export function applyResolveConfig(config, resolveConfig, projectContext) {
|
|
|
61
42
|
agentConfig.model = model;
|
|
62
43
|
config.agent[name] = agentConfig;
|
|
63
44
|
}
|
|
64
|
-
if (resolveConfig.context7 !== false) {
|
|
65
|
-
config.mcp ??= {};
|
|
66
|
-
config.mcp.context7 ??= {
|
|
67
|
-
type: "remote",
|
|
68
|
-
url: "https://mcp.context7.com/mcp",
|
|
69
|
-
};
|
|
70
|
-
}
|
|
71
45
|
if (resolveConfig.commands) {
|
|
72
46
|
config.command ??= {};
|
|
73
47
|
config.command["resolve"] ??= {
|
|
@@ -114,14 +88,13 @@ export function buildContextInjection(ctx) {
|
|
|
114
88
|
}
|
|
115
89
|
export function defaultResolveConfig() {
|
|
116
90
|
return {
|
|
117
|
-
profile: "mix",
|
|
118
91
|
models: {},
|
|
119
92
|
agents: {},
|
|
120
93
|
preserveNative: true,
|
|
121
|
-
context7: true,
|
|
122
94
|
commands: false,
|
|
123
95
|
autoApprove: true,
|
|
124
96
|
autoUpdate: true,
|
|
97
|
+
permissions: {},
|
|
125
98
|
};
|
|
126
99
|
}
|
|
127
100
|
export function mergeResolveConfig(...configs) {
|
|
@@ -129,16 +102,16 @@ export function mergeResolveConfig(...configs) {
|
|
|
129
102
|
for (const config of configs) {
|
|
130
103
|
if (!config)
|
|
131
104
|
continue;
|
|
132
|
-
result.profile = config.profile ?? result.profile;
|
|
133
105
|
result.tier = config.tier ?? result.tier;
|
|
134
106
|
result.enabled = config.enabled ?? result.enabled;
|
|
135
107
|
result.preserveNative = config.preserveNative ?? result.preserveNative;
|
|
136
|
-
result.context7 = config.context7 ?? result.context7;
|
|
137
108
|
result.commands = config.commands ?? result.commands;
|
|
138
109
|
result.autoApprove = config.autoApprove ?? result.autoApprove;
|
|
139
110
|
result.maxParallelSubagents = config.maxParallelSubagents ?? result.maxParallelSubagents;
|
|
140
111
|
result.autoUpdate = config.autoUpdate ?? result.autoUpdate;
|
|
141
112
|
result.language = config.language ?? result.language;
|
|
113
|
+
result.singleAgentMode = config.singleAgentMode ?? result.singleAgentMode;
|
|
114
|
+
result.permissions = { ...result.permissions, ...config.permissions };
|
|
142
115
|
result.models = { ...result.models, ...config.models };
|
|
143
116
|
result.agents = mergeAgents(result.agents, config.agents);
|
|
144
117
|
}
|
|
@@ -223,21 +196,14 @@ export function normalizeResolveConfig(value, source) {
|
|
|
223
196
|
}
|
|
224
197
|
if (config.preserveNative !== undefined)
|
|
225
198
|
result.preserveNative = expectBoolean(config.preserveNative, `${source}.preserveNative`);
|
|
226
|
-
if (config.context7 !== undefined)
|
|
227
|
-
result.context7 = expectBoolean(config.context7, `${source}.context7`);
|
|
228
199
|
if (config.commands !== undefined)
|
|
229
200
|
result.commands = expectBoolean(config.commands, `${source}.commands`);
|
|
230
201
|
if (config.autoApprove !== undefined)
|
|
231
202
|
result.autoApprove = expectBoolean(config.autoApprove, `${source}.autoApprove`);
|
|
232
203
|
if (config.autoUpdate !== undefined)
|
|
233
204
|
result.autoUpdate = expectBoolean(config.autoUpdate, `${source}.autoUpdate`);
|
|
234
|
-
if (config.
|
|
235
|
-
|
|
236
|
-
if (!VALID_PROFILES.has(profile)) {
|
|
237
|
-
throw new Error(`Unknown profile "${profile}" in ${source}.profile. Valid profiles: ${[...VALID_PROFILES].join(", ")}`);
|
|
238
|
-
}
|
|
239
|
-
result.profile = profile;
|
|
240
|
-
}
|
|
205
|
+
if (config.singleAgentMode !== undefined)
|
|
206
|
+
result.singleAgentMode = expectBoolean(config.singleAgentMode, `${source}.singleAgentMode`);
|
|
241
207
|
if (config.tier !== undefined) {
|
|
242
208
|
const tier = expectString(config.tier, `${source}.tier`);
|
|
243
209
|
if (!VALID_TIERS.has(tier)) {
|
|
@@ -259,10 +225,26 @@ export function normalizeResolveConfig(value, source) {
|
|
|
259
225
|
}
|
|
260
226
|
result.maxParallelSubagents = limit;
|
|
261
227
|
}
|
|
228
|
+
if (config.permissions !== undefined)
|
|
229
|
+
result.permissions = normalizePermissions(config.permissions, `${source}.permissions`);
|
|
262
230
|
if (config.config !== undefined)
|
|
263
231
|
result.config = expectString(config.config, `${source}.config`);
|
|
264
232
|
return result;
|
|
265
233
|
}
|
|
234
|
+
export function normalizePermissions(value, source) {
|
|
235
|
+
const permissions = expectObject(value, source);
|
|
236
|
+
const result = {};
|
|
237
|
+
for (const key of Object.keys(permissions)) {
|
|
238
|
+
if (!VALID_PERMISSIONS_KEYS.has(key)) {
|
|
239
|
+
throw new Error(`Unknown permissions key "${key}" in ${source}. Valid: ${[...VALID_PERMISSIONS_KEYS].join(", ")}`);
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
if (permissions.allowGitReset !== undefined)
|
|
243
|
+
result.allowGitReset = expectBoolean(permissions.allowGitReset, `${source}.allowGitReset`);
|
|
244
|
+
if (permissions.allowGitClean !== undefined)
|
|
245
|
+
result.allowGitClean = expectBoolean(permissions.allowGitClean, `${source}.allowGitClean`);
|
|
246
|
+
return result;
|
|
247
|
+
}
|
|
266
248
|
export function normalizeAgentConfig(value, source) {
|
|
267
249
|
const config = expectObject(value, source);
|
|
268
250
|
for (const key of Object.keys(config)) {
|
|
@@ -384,20 +366,21 @@ export async function loadResolveConfig(directory, opencodeConfig, options) {
|
|
|
384
366
|
return mergeResolveConfig(defaultResolveConfig(), fileConfig, pluginOptions);
|
|
385
367
|
}
|
|
386
368
|
export const VALID_TOP_LEVEL_KEYS = new Set([
|
|
387
|
-
"profile",
|
|
388
369
|
"tier",
|
|
389
370
|
"enabled",
|
|
390
371
|
"models",
|
|
391
372
|
"agents",
|
|
392
373
|
"preserveNative",
|
|
393
|
-
"context7",
|
|
394
374
|
"commands",
|
|
395
375
|
"autoApprove",
|
|
396
376
|
"maxParallelSubagents",
|
|
397
377
|
"autoUpdate",
|
|
398
378
|
"language",
|
|
379
|
+
"singleAgentMode",
|
|
380
|
+
"permissions",
|
|
399
381
|
"config",
|
|
400
382
|
]);
|
|
383
|
+
export const VALID_PERMISSIONS_KEYS = new Set(["allowGitReset", "allowGitClean"]);
|
|
401
384
|
export const VALID_AGENT_KEYS = new Set([
|
|
402
385
|
"enabled",
|
|
403
386
|
"model",
|