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/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: 20,
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: 8,
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: 30,
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: 6,
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: 10,
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 const GLM_CODER_PROMPT = [
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
- : undefined;
261
- const parallelRule = limit === undefined
262
- ? "No hard cap. Fan out only for genuinely independent work, and back off immediately on rate-limit errors."
263
- : limit === 1
264
- ? "Dispatch ONE coder at a time. Wait for it to finish."
265
- : `Dispatch up to ${limit} coder(s) concurrently. Wait for in-flight coders before dispatching more.`;
266
- return [
267
- "You are Resolver (GLM profile), the token-efficient orchestrator for OpenCode Resolve.",
268
- "ZAI Coding Plan — quota is finite. Minimize unnecessary reads and dispatches.",
269
- "",
270
- `Parallel: ${parallelRule}`,
271
- "Dispatch coder with: TASK (atomic goal), OUTCOME (success criteria), MUST DO, MUST NOT DO, CONTEXT (files/patterns).",
272
- "After EVERY coder return: verify it works + follows codebase patterns. If not → re-dispatch with fix.",
273
- "INTELLIGENT RECOVERY: On verify failure, dispatch debugger FIRST to diagnose root cause, THEN re-dispatch coder with precise fix. Do NOT blindly retry.",
274
- "Trivial fixes → apply yourself. No subagent needed.",
275
- "3 consecutive failures → STOP, REVERT, REPORT, ASK user.",
276
- "10+ failures on same task call architect to rethink the approach before continuing.",
277
- "",
278
- "If piloci MCP available: piloci_recall before inspecting code, piloci_memory after learning something non-obvious.",
279
- "",
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
- "After non-trivial work: ask user to capture lesson → HARNESS.md (infra) or AGENTS.md (agent behavior).",
282
- "",
283
- "NEVER: as any / @ts-ignore / leave code broken / delete failing tests / commit without request / git add . / git add -A / stage unrelated files.",
284
- "",
285
- "Specialists: explorer (scope unknown), reviewer (verification gap), debugger (verify failure diagnosis), planner (user asks for plan). No deep-reviewer.",
286
- "",
287
- "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.",
288
- ].join("\n");
289
- }
290
- export function buildGPTResolverPrompt() {
291
- return [
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
- "Drive tasks to verified resolution with minimal context and fewest LLM calls.",
346
- "You and Coder form the verified resolve loop.",
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
- `Parallel: ${parallelRule}`,
349
- "Dispatch coder with: TASK (atomic goal), OUTCOME (success criteria), MUST DO, MUST NOT DO, CONTEXT (files/patterns).",
350
- "After EVERY coder return: verify it works + follows codebase patterns. If not → re-dispatch with fix.",
351
- "INTELLIGENT RECOVERY: On verify failure, dispatch debugger FIRST to diagnose root cause, THEN re-dispatch coder with precise fix. Do NOT blindly retry.",
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", "codex", "gpt", "glm", "explorer", "reviewer", "deep-reviewer", "planner", "debugger", "researcher"],
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, buildGLMResolverPrompt, GLM_CODER_PROMPT, buildGPTResolverPrompt, GPT_CODER_PROMPT, buildResolverPrompt, VALID_AGENT_NAME_SET, DEFAULT_MODELS, DEFAULT_ENABLED, VALID_AGENT_NAMES, GLM_ENABLED, GPT_ENABLED, TIER_ENABLED, GLM_AGENT_OVERRIDES, GPT_AGENT_OVERRIDES, VALID_MODEL_ALIAS_SET, VALID_PROFILES, VALID_TIERS } from "./agents.js";
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 ?? (profileEnabled ?? DEFAULT_ENABLED));
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 (isGLM) {
34
- if (name === "resolver")
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
- else if (isGPT) {
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.profile !== undefined) {
235
- const profile = expectString(config.profile, `${source}.profile`);
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",