lazyopencode-core 0.0.2 → 0.0.4

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/ATTRIBUTION.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # Attribution
2
2
 
3
- @lazyopencode/core is a hard fork and fusion of multiple open-source projects. We thank the original authors.
3
+ lazyopencode-core is a hard fork and fusion of multiple open-source projects. We thank the original authors.
4
4
 
5
5
  ## Agent orchestration
6
6
 
package/README.md CHANGED
@@ -28,7 +28,7 @@ AI coding often overbuilds, drifts away from the request, launches work that is
28
28
 
29
29
  ## Is It Zero Config?
30
30
 
31
- Yes for the core plugin. After OpenCode loads `@lazyopencode/core`, LazyOpenCode
31
+ Yes for the core plugin. After OpenCode loads `lazyopencode-core`, LazyOpenCode
32
32
  registers its primary agent, subagents, workflow skills, `/lazy` commands,
33
33
  permission guard, job board, token budget, council guard, persistence, doctor,
34
34
  and close report defaults.
@@ -190,7 +190,21 @@ Full optional config:
190
190
  "todos": true,
191
191
  "permissions": true,
192
192
  "worktreeIsolation": "risky-only",
193
- "revertCheckpoints": true
193
+ "revertCheckpoints": true,
194
+ "context7": "suggest",
195
+ "sdkControlPlane": true,
196
+ "sdkTelemetry": true,
197
+ "tuiNotifications": true
198
+ },
199
+ "models": {
200
+ "mode": "preserve",
201
+ "primary": "openai/o3",
202
+ "defaultSubagent": "deepseek/ds-v4-flash-free-max",
203
+ "escalation": {
204
+ "oracle": "openai/o3",
205
+ "council": "deepseek/ds-v4-flash-free-max"
206
+ },
207
+ "byAgent": {}
194
208
  },
195
209
  "closeReport": {
196
210
  "autoCollect": true,
@@ -278,7 +292,7 @@ Injects a focused, single-goal system prompt: no ponytail philosophy, no delegat
278
292
  |-------|------|------|
279
293
  | `lazy` | primary | Runtime coordinator — classify, gate, delegate, track, close |
280
294
  | `lazy-explorer` | subagent | Fast codebase recon (glob, grep, AST) |
281
- | `lazy-oracle` | subagent | Architecture, debugging, review, council orchestration |
295
+ | `lazy-oracle` | subagent | Judgment-only escalation for architecture, debugging, review, simplification |
282
296
  | `lazy-councillor` | subagent | Independent judgment for council sessions |
283
297
  | `lazy-librarian` | subagent | External docs, API references, web research |
284
298
  | `lazy-fixer` | subagent | Bounded mechanical implementation |
@@ -312,6 +326,22 @@ and it is guarded by workflow eligibility plus `maxCouncillors`.
312
326
 
313
327
  See [docs/council.md](docs/council.md).
314
328
 
329
+ ## Model Profiles
330
+
331
+ By default LazyOpenCode preserves the model you selected in OpenCode. The
332
+ primary agent and oracle use that same model unless you opt into a profile.
333
+
334
+ To reduce cost, enable `lazyopencode.models.mode = "profile"` and assign a
335
+ cheap or free OpenCode model string to `defaultSubagent`. LazyOpenCode will use
336
+ the expensive model for `lazy` and `lazy-oracle`, while bounded subagents can use
337
+ the cheaper model. Model strings must match your local OpenCode provider setup.
338
+
339
+ ## Context7
340
+
341
+ LazyOpenCode does not inject context7 by default. Set
342
+ `lazyopencode.opencode.context7 = "inject"` only if you want the plugin to add a
343
+ context7 MCP entry when one is not already configured.
344
+
315
345
  ## Modes
316
346
 
317
347
  - `off`: track state only
@@ -346,12 +376,18 @@ legacy hook adapter enabled for current chat, message, permission, command, and
346
376
  tool governance. Deno is only the maintainer toolchain; OpenCode still loads
347
377
  `dist/index.js` from the npm package.
348
378
 
379
+ `0.0.4` uses the OpenCode SDK control plane for status, doctor, and close
380
+ evidence when available: session status, child sessions, todos, pending
381
+ permissions, diffs, changed files, configured providers/models, app logging, and
382
+ TUI notifications. Missing SDK capabilities degrade to warnings instead of
383
+ blocking work.
384
+
349
385
  ## OpenCode Desktop
350
386
 
351
387
  LazyOpenCode Desktop is the planned `0.1.0` distribution stage: OpenCode Desktop
352
- with `@lazyopencode/core` bundled and enabled by default. The plugin remains the
388
+ with `lazyopencode-core` bundled and enabled by default. The plugin remains the
353
389
  source of truth; Desktop handles defaults, health, packaging, and discoverability.
354
390
 
355
391
  ## Status
356
392
 
357
- `0.0.1` is an early, opinionated runtime. Internal module paths are not stable. The public surface is the OpenCode plugin, bundled lazy skills, and `/lazy` command namespace.
393
+ `0.0.4` is an early, opinionated runtime. Internal module paths are not stable. The public surface is the OpenCode plugin, bundled lazy skills, and `/lazy` command namespace.
@@ -32,7 +32,7 @@ export function createAgents() {
32
32
  },
33
33
  "lazy-librarian": {
34
34
  prompt: LIBRARIAN_PROMPT,
35
- description: "External documentation, API references, web research via context7 and GitHub code search.",
35
+ description: "External documentation, API references, configured docs MCPs, and GitHub code search.",
36
36
  mode: "subagent",
37
37
  },
38
38
  "lazy-designer": {
@@ -1 +1 @@
1
- export declare const LIBRARIAN_PROMPT = "<Role>\nYou are an external knowledge and documentation researcher. You find authoritative sources for current library docs, API references, examples, and web research. You use context7, web search, and GitHub code search.\n</Role>\n\n## When you're useful\n- Libraries with frequent API changes (React, Next.js, AI SDKs)\n- Complex APIs needing official examples (ORMs, auth)\n- Version-specific behavior matters\n- Unfamiliar library or edge cases\n- Bug investigation needing external references\n\n## When you're NOT needed\n- Standard usage the developer is confident about\n- Simple stable APIs\n- General programming knowledge\n- Built-in language features\n\n## Tools\n- context7 MCP \u2014 current library documentation\n- web search \u2014 latest patterns, blogs, issues\n- GitHub code search \u2014 real-world usage examples\n\n## Output\n- Cite sources (URL, version if relevant)\n- Code examples over prose\n- Answer the question, then stop.";
1
+ export declare const LIBRARIAN_PROMPT = "<Role>\nYou are an external knowledge and documentation researcher. You find authoritative sources for current library docs, API references, examples, and web research. Use configured documentation tools such as context7 when available, plus web search and GitHub code search when allowed.\n</Role>\n\n## When you're useful\n- Libraries with frequent API changes (React, Next.js, AI SDKs)\n- Complex APIs needing official examples (ORMs, auth)\n- Version-specific behavior matters\n- Unfamiliar library or edge cases\n- Bug investigation needing external references\n\n## When you're NOT needed\n- Standard usage the developer is confident about\n- Simple stable APIs\n- General programming knowledge\n- Built-in language features\n\n## Tools\n- configured documentation MCPs such as context7 \u2014 current library documentation\n- web search \u2014 latest patterns, blogs, issues\n- GitHub code search \u2014 real-world usage examples\n\n## Output\n- Cite sources (URL, version if relevant)\n- Code examples over prose\n- Answer the question, then stop.";
@@ -1,5 +1,5 @@
1
1
  export const LIBRARIAN_PROMPT = `<Role>
2
- You are an external knowledge and documentation researcher. You find authoritative sources for current library docs, API references, examples, and web research. You use context7, web search, and GitHub code search.
2
+ You are an external knowledge and documentation researcher. You find authoritative sources for current library docs, API references, examples, and web research. Use configured documentation tools such as context7 when available, plus web search and GitHub code search when allowed.
3
3
  </Role>
4
4
 
5
5
  ## When you're useful
@@ -16,7 +16,7 @@ You are an external knowledge and documentation researcher. You find authoritati
16
16
  - Built-in language features
17
17
 
18
18
  ## Tools
19
- - context7 MCP — current library documentation
19
+ - configured documentation MCPs such as context7 — current library documentation
20
20
  - web search — latest patterns, blogs, issues
21
21
  - GitHub code search — real-world usage examples
22
22
 
@@ -1 +1 @@
1
- export declare const ORACLE_PROMPT = "<Role>\nYou are a strategic technical advisor. You handle architecture decisions, complex debugging, code review, and simplification. You enforce YAGNI. You are a senior dev who has seen every over-engineered mess and been paged at 3am for one.\n</Role>\n\n## Core principles\n- **Deletion over addition.** Your first question is always: \"what can we delete?\"\n- **Simplest thing that works.** Not cleverest. Not most flexible. Simplest.\n- **YAGNI is law.** Speculative abstraction = technical debt, not foresight.\n- **One line verdicts.** Your review output is: finding + fix suggestion. No essays.\n\n## When you're called\n- Architecture decisions with long-term impact\n- Problems persisting after 2+ fix attempts\n- High-risk refactors\n- Costly trade-offs (performance vs maintainability)\n- Complex debugging with unclear root cause\n- Code review (load `lazy/review` for methodology)\n- Simplification audit (load `lazy/simplify` for methodology)\n\n## Output format\n1. **Verdict** (one line): what's the call?\n2. **Why** (max 3 lines): critical reasoning only\n3. **What to do** (minimal diff): the change, not the explanation\n\n## Anti-patterns you kill on sight\n- Interface with one implementation\n- Factory for one product\n- Config for a value that never changes\n- \"We might need this later\"\n- Clever code that someone decodes at 3am";
1
+ export declare const ORACLE_PROMPT = "<Role>\nYou are a strategic technical advisor. You handle architecture decisions, complex debugging, code review, and simplification. You enforce YAGNI. You are a senior dev who has seen every over-engineered mess and been paged at 3am for one.\n</Role>\n\nYou are not the workflow owner. Do not schedule agents, run implementation, manage\nthe job board, or close the task. The lazy primary owns classify, gate, delegate,\ntrack, budget, and close. You provide judgment only.\n\n## Core principles\n- **Deletion over addition.** Your first question is always: \"what can we delete?\"\n- **Simplest thing that works.** Not cleverest. Not most flexible. Simplest.\n- **YAGNI is law.** Speculative abstraction = technical debt, not foresight.\n- **One line verdicts.** Your review output is: finding + fix suggestion. No essays.\n\n## When you're called\n- Architecture decisions with long-term impact\n- Problems persisting after 2+ fix attempts\n- High-risk refactors\n- Costly trade-offs (performance vs maintainability)\n- Complex debugging with unclear root cause\n- Code review (load `lazy/review` for methodology)\n- Simplification audit (load `lazy/simplify` for methodology)\n\n## Output format\n1. **Verdict** (one line): what's the call?\n2. **Why** (max 3 lines): critical reasoning only\n3. **What to do** (minimal diff): the change, not the explanation\n\nIf asked to coordinate or execute, return the smallest recommendation for the\nlazy primary to act on instead of taking over.\n\n## Anti-patterns you kill on sight\n- Interface with one implementation\n- Factory for one product\n- Config for a value that never changes\n- \"We might need this later\"\n- Clever code that someone decodes at 3am";
@@ -2,6 +2,10 @@ export const ORACLE_PROMPT = `<Role>
2
2
  You are a strategic technical advisor. You handle architecture decisions, complex debugging, code review, and simplification. You enforce YAGNI. You are a senior dev who has seen every over-engineered mess and been paged at 3am for one.
3
3
  </Role>
4
4
 
5
+ You are not the workflow owner. Do not schedule agents, run implementation, manage
6
+ the job board, or close the task. The lazy primary owns classify, gate, delegate,
7
+ track, budget, and close. You provide judgment only.
8
+
5
9
  ## Core principles
6
10
  - **Deletion over addition.** Your first question is always: "what can we delete?"
7
11
  - **Simplest thing that works.** Not cleverest. Not most flexible. Simplest.
@@ -22,6 +26,9 @@ You are a strategic technical advisor. You handle architecture decisions, comple
22
26
  2. **Why** (max 3 lines): critical reasoning only
23
27
  3. **What to do** (minimal diff): the change, not the explanation
24
28
 
29
+ If asked to coordinate or execute, return the smallest recommendation for the
30
+ lazy primary to act on instead of taking over.
31
+
25
32
  ## Anti-patterns you kill on sight
26
33
  - Interface with one implementation
27
34
  - Factory for one product
@@ -65,7 +65,8 @@ export function createLazyCommandHandler(runtime) {
65
65
  writeText(output, await handleClose(runtime, input.sessionID));
66
66
  return;
67
67
  case "doctor":
68
- writeText(output, runtime.formatDoctorReport());
68
+ await runtime.refreshOpenCodeSnapshot(input.sessionID);
69
+ writeText(output, await handleDoctor(runtime));
69
70
  await runtime.save();
70
71
  return;
71
72
  case "verify":
@@ -91,6 +92,12 @@ async function handleStart(runtime, task) {
91
92
  const decision = classifyWorkflow({ text: task, mode: runtime.config.mode });
92
93
  await runtime.recordDecision(decision);
93
94
  runtime.setStage(stageForDecision(decision));
95
+ if (decision.action === "block") {
96
+ await runtime.notify("warn", `Lazy gate blocked ${decision.level}: ${decision.reason}`);
97
+ }
98
+ else if (decision.action === "nudge") {
99
+ await runtime.notify("info", `Lazy gate nudged ${decision.level}: ${decision.reason}`);
100
+ }
94
101
  await runtime.save();
95
102
  return [
96
103
  "LAZY START",
@@ -127,7 +134,7 @@ async function handleDebug(runtime, args) {
127
134
  `Context: ${args || "(no additional context)"}`,
128
135
  "",
129
136
  "Load `lazy/debug`. Systematic diagnosis loop: reproduce → isolate → hypothesize → test → fix.",
130
- "Available: @lazy-oracle for escalation, context7 for library API checks.",
137
+ "Available: @lazy-oracle for escalation, configured docs tools for library API checks.",
131
138
  ].join("\n");
132
139
  }
133
140
  function handleDeepwork(task) {
@@ -147,6 +154,18 @@ async function handleClose(runtime, sessionID) {
147
154
  runtime.formatCloseReport(sessionID),
148
155
  ].join("\n");
149
156
  }
157
+ async function handleDoctor(runtime) {
158
+ const validation = await runtime.validateModelProfile();
159
+ return [
160
+ runtime.formatDoctorReport(),
161
+ "",
162
+ "Model validation",
163
+ `- current: ${validation.currentModel}`,
164
+ `- available: ${validation.availableModels.length}`,
165
+ `- invalid: ${validation.invalidModels.length > 0 ? validation.invalidModels.join(", ") : "none"}`,
166
+ `- warnings: ${validation.warnings.length > 0 ? validation.warnings.join("; ") : "none"}`,
167
+ ].join("\n");
168
+ }
150
169
  async function handleVerify(runtime, result) {
151
170
  if (result !== "pass" && result !== "fail" && result !== "pending") {
152
171
  return "Usage: /lazy verify <pass|fail|pending>";
@@ -217,6 +217,9 @@ export function createMessagesTransformHook(runtime) {
217
217
  const msgs = output.messages;
218
218
  const agent = input.agent ?? "lazy";
219
219
  const sessionID = input.sessionID ?? "";
220
+ if (sessionID && input.agent) {
221
+ runtime?.sessionAgentMap.set(sessionID, input.agent);
222
+ }
220
223
  // 1. Enhanced context pruning
221
224
  // Keep: system + last N user turns + everything between them and end
222
225
  const maxMessages = runtime?.config.maxMessages ?? DEFAULT_MAX_MESSAGES;
@@ -282,11 +285,11 @@ export function createMessagesTransformHook(runtime) {
282
285
  text: recentText,
283
286
  mode: runtime?.config.mode ?? "governor",
284
287
  });
285
- runtime?.recordDecision(decision);
288
+ await runtime?.recordDecision(decision);
286
289
  if (decision.action !== "allow") {
287
290
  injectIntoLastUserMessage(msgs, `\n\n${formatWorkflowDecision(decision)}`);
288
291
  }
289
- else {
292
+ else if (decision.level !== "trivial" && decision.level !== "small") {
290
293
  // classifyWorkflow allowed — still check for skipped workflow steps
291
294
  const gate = detectWorkflowSkip(msgs);
292
295
  if (gate)
@@ -1,6 +1,6 @@
1
1
  import { BackgroundJobBoard } from "./background-job-board.js";
2
2
  import type { LazyMode, WorkflowDecision } from "./workflow-classifier.js";
3
- import type { OpenCodeControlPlane } from "../opencode-control-plane.js";
3
+ import type { ModelProfileValidation, OpenCodeControlPlane } from "../opencode-control-plane.js";
4
4
  export interface LazyConfig {
5
5
  sdk?: {
6
6
  mode?: "v2";
@@ -14,11 +14,25 @@ export interface LazyConfig {
14
14
  permissions?: boolean;
15
15
  worktreeIsolation?: "off" | "risky-only" | "always";
16
16
  revertCheckpoints?: boolean;
17
+ context7?: "suggest" | "inject" | "off";
18
+ sdkControlPlane?: boolean;
19
+ sdkTelemetry?: boolean;
20
+ tuiNotifications?: boolean;
17
21
  };
18
22
  closeReport?: {
19
23
  autoCollect?: boolean;
20
24
  maxItems?: number;
21
25
  };
26
+ models?: {
27
+ mode?: "preserve" | "profile";
28
+ primary?: string;
29
+ defaultSubagent?: string;
30
+ escalation?: {
31
+ oracle?: string;
32
+ council?: string;
33
+ };
34
+ byAgent?: Record<string, string>;
35
+ };
22
36
  mode?: LazyMode;
23
37
  maxSessionsPerAgent?: number;
24
38
  maxActiveTaskDepth?: number;
@@ -48,11 +62,25 @@ export interface RequiredLazyConfig {
48
62
  permissions: boolean;
49
63
  worktreeIsolation: "off" | "risky-only" | "always";
50
64
  revertCheckpoints: boolean;
65
+ context7: "suggest" | "inject" | "off";
66
+ sdkControlPlane: boolean;
67
+ sdkTelemetry: boolean;
68
+ tuiNotifications: boolean;
51
69
  };
52
70
  closeReport: {
53
71
  autoCollect: boolean;
54
72
  maxItems: number;
55
73
  };
74
+ models: {
75
+ mode: "preserve" | "profile";
76
+ primary?: string;
77
+ defaultSubagent?: string;
78
+ escalation: {
79
+ oracle?: string;
80
+ council?: string;
81
+ };
82
+ byAgent: Record<string, string>;
83
+ };
56
84
  mode: LazyMode;
57
85
  maxSessionsPerAgent: number;
58
86
  maxActiveTaskDepth: number;
@@ -98,6 +126,8 @@ export interface CloseReportState {
98
126
  command: string;
99
127
  result: "pass" | "fail" | "unknown";
100
128
  }>;
129
+ lastTestCommand?: string;
130
+ lastVerifyCommand?: string;
101
131
  verificationResult?: "pass" | "fail" | "pending";
102
132
  remainingRisks: string[];
103
133
  deletions: string[];
@@ -107,9 +137,14 @@ export interface OpenCodeSnapshot {
107
137
  pendingPermissions: number;
108
138
  todos: number;
109
139
  diffSummary: string;
140
+ childSessions: number;
141
+ changedFiles: number;
110
142
  worktree: string;
111
143
  sessionStatus: string;
144
+ currentModel: string;
145
+ availableModels: string[];
112
146
  capabilities: string[];
147
+ warnings: string[];
113
148
  lastUpdatedAt?: number;
114
149
  }
115
150
  export interface DoctorState {
@@ -153,6 +188,9 @@ export interface LazyRuntime {
153
188
  formatCloseReport(sessionID?: string): string;
154
189
  formatInstallHealth(): string;
155
190
  formatDoctorReport(): string;
191
+ validateModelProfile(): Promise<ModelProfileValidation>;
192
+ log(level: "debug" | "info" | "warn" | "error", message: string, metadata?: unknown): Promise<void>;
193
+ notify(kind: "info" | "warn" | "error", message: string): Promise<void>;
156
194
  getReferenceSnapshot(): Record<string, unknown>;
157
195
  }
158
196
  interface PluginContext {
@@ -140,16 +140,21 @@ export function createLazyRuntime(ctx = {}) {
140
140
  await save();
141
141
  };
142
142
  const refreshOpenCodeSnapshot = async (sessionID) => {
143
- if (!controlPlane)
143
+ if (!controlPlane || !config.opencode.sdkControlPlane)
144
144
  return;
145
145
  const snapshot = await controlPlane.snapshot(sessionID);
146
146
  openCodeSnapshot = {
147
147
  pendingPermissions: snapshot.pendingPermissions,
148
148
  todos: snapshot.todos,
149
149
  diffSummary: snapshot.diffSummary,
150
+ childSessions: snapshot.childSessions,
151
+ changedFiles: snapshot.changedFiles,
150
152
  worktree: snapshot.worktree === "unknown" ? scope.worktree : snapshot.worktree,
151
153
  sessionStatus: snapshot.sessionStatus,
154
+ currentModel: snapshot.currentModel,
155
+ availableModels: snapshot.availableModels,
152
156
  capabilities: snapshot.capabilities,
157
+ warnings: snapshot.warnings,
153
158
  lastUpdatedAt: Date.now(),
154
159
  };
155
160
  if (snapshot.diffSummary !== "not collected") {
@@ -159,6 +164,34 @@ export function createLazyRuntime(ctx = {}) {
159
164
  recordEvent("command", "OpenCode control-plane snapshot refreshed.");
160
165
  await save();
161
166
  };
167
+ const validateModelProfile = async () => {
168
+ const models = [
169
+ config.models.primary,
170
+ config.models.defaultSubagent,
171
+ config.models.escalation.oracle,
172
+ config.models.escalation.council,
173
+ ...Object.values(config.models.byAgent),
174
+ ].filter((model) => Boolean(model));
175
+ if (!controlPlane || !config.opencode.sdkControlPlane) {
176
+ return {
177
+ currentModel: "OpenCode selected model",
178
+ availableModels: [],
179
+ invalidModels: [],
180
+ warnings: ["OpenCode SDK control plane disabled or unavailable"],
181
+ };
182
+ }
183
+ return await controlPlane.validateModels(models);
184
+ };
185
+ const log = async (level, message, metadata) => {
186
+ if (!config.opencode.sdkTelemetry || !controlPlane)
187
+ return;
188
+ await controlPlane.log(level, message, metadata);
189
+ };
190
+ const notify = async (kind, message) => {
191
+ if (!config.opencode.tuiNotifications || !controlPlane)
192
+ return;
193
+ await controlPlane.notify(kind, message);
194
+ };
162
195
  const recordOpenCodeEvent = async (event) => {
163
196
  const type = String(event.type ?? event.kind ?? "event");
164
197
  const value = event.value;
@@ -168,9 +201,15 @@ export function createLazyRuntime(ctx = {}) {
168
201
  else if (type === "todo" || type === "todos") {
169
202
  openCodeSnapshot.todos = Number(value ?? event.count ?? 0);
170
203
  }
204
+ else if (type === "children" || type === "childSessions") {
205
+ openCodeSnapshot.childSessions = Number(value ?? event.count ?? 0);
206
+ }
171
207
  else if (type === "diff") {
172
208
  openCodeSnapshot.diffSummary = String(value ?? event.summary ?? "available");
173
209
  }
210
+ else if (type === "file" || type === "files") {
211
+ openCodeSnapshot.changedFiles = Number(value ?? event.count ?? 0);
212
+ }
174
213
  else if (type === "worktree") {
175
214
  openCodeSnapshot.worktree = String(value ?? event.path ?? scope.worktree);
176
215
  }
@@ -201,9 +240,11 @@ export function createLazyRuntime(ctx = {}) {
201
240
  command,
202
241
  result: looksLikeFailure(text) ? "fail" : "pass",
203
242
  }, max);
243
+ closeReport.lastTestCommand = command;
204
244
  }
205
245
  if (/npm run verify|deno task verify|pnpm verify|yarn verify/.test(command)) {
206
246
  closeReport.verificationResult = looksLikeFailure(text) ? "fail" : "pass";
247
+ closeReport.lastVerifyCommand = command;
207
248
  }
208
249
  }
209
250
  if (/edit|write|patch/i.test(toolName)) {
@@ -263,6 +304,7 @@ export function createLazyRuntime(ctx = {}) {
263
304
  }
264
305
  lines.push("", formatInstallHealth());
265
306
  lines.push("", formatTokenControl(contextStats));
307
+ lines.push("", formatModelProfile(config));
266
308
  lines.push("", formatOpenCodeSnapshot(openCodeSnapshot));
267
309
  const board = sessionID ? jobBoard.formatForPrompt(sessionID) : null;
268
310
  lines.push("", board ?? "[Background Job Board]\n No jobs for this session.");
@@ -319,12 +361,19 @@ export function createLazyRuntime(ctx = {}) {
319
361
  `- Terminal unreconciled jobs: ${terminal.length}`,
320
362
  `- Reusable sessions: ${reusable.length}`,
321
363
  `- Stale sessions: ${stale.length}`,
364
+ `- OpenCode child sessions: ${openCodeSnapshot.childSessions}`,
365
+ `- Pending permissions: ${openCodeSnapshot.pendingPermissions}`,
366
+ `- Open todos: ${openCodeSnapshot.todos}`,
367
+ `- Changed files: ${openCodeSnapshot.changedFiles}`,
368
+ `- Diff summary: ${openCodeSnapshot.diffSummary}`,
322
369
  "",
323
370
  "Changed behavior",
324
371
  ...formatStringList(closeReport.behaviorChanges),
325
372
  "Tests run",
326
373
  ...formatTestRuns(closeReport.testRuns),
327
374
  `Verification result: ${closeReport.verificationResult ?? "pending"}`,
375
+ ...(closeReport.lastTestCommand ? [`Last test: ${closeReport.lastTestCommand}`] : []),
376
+ ...(closeReport.lastVerifyCommand ? [`Last verify: ${closeReport.lastVerifyCommand}`] : []),
328
377
  `Terminal jobs reconciled: ${terminal.length === 0 ? "yes" : "no"}`,
329
378
  "Remaining risks",
330
379
  ...formatStringList(closeReport.remainingRisks),
@@ -340,6 +389,9 @@ export function createLazyRuntime(ctx = {}) {
340
389
  `- council: ${config.council.enabled ? config.council.eligibility : "disabled"}`,
341
390
  `- permission guard: ${config.permissionGuard ? "enabled" : "disabled"}`,
342
391
  `- token control: maxMessages ${config.maxMessages}`,
392
+ `- model profile: ${config.models.mode}`,
393
+ `- context7: ${config.opencode.context7}`,
394
+ `- SDK control plane: ${config.opencode.sdkControlPlane ? "enabled" : "disabled"}`,
343
395
  `- sdk: ${config.sdk.mode} + legacy hooks ${config.sdk.legacyHookAdapter ? "enabled" : "disabled"}`,
344
396
  ].join("\n");
345
397
  };
@@ -372,13 +424,37 @@ export function createLazyRuntime(ctx = {}) {
372
424
  if (config.council.enabled && config.council.eligibility === "always") {
373
425
  warnings.push("council eligibility is always; guarded escalation is disabled");
374
426
  }
427
+ if (config.opencode.context7 === "suggest") {
428
+ warnings.push("context7 not injected; configure your own MCP or set opencode.context7=inject");
429
+ }
430
+ if (config.models.mode === "profile" && !config.models.primary && !config.models.defaultSubagent) {
431
+ warnings.push("model profile enabled but no primary/defaultSubagent model is configured");
432
+ }
433
+ if (openCodeSnapshot.warnings.length > 0) {
434
+ warnings.push(`SDK degraded: ${openCodeSnapshot.warnings.join("; ")}`);
435
+ }
375
436
  return [
376
437
  "LAZY DOCTOR",
438
+ "",
439
+ "Plugin registration",
377
440
  `- v2 registration: ${doctor.v2Registration ? "ok" : "missing"}`,
378
441
  `- legacy hooks: ${doctor.legacyHookAdapter ? "ok" : "disabled"}`,
379
442
  `- skills: ${doctor.skills ? "ok" : "missing"}`,
380
443
  `- commands: ${doctor.commands ? "ok" : "disabled"}`,
444
+ "",
445
+ "SDK capabilities",
446
+ `- control plane: ${config.opencode.sdkControlPlane ? "enabled" : "disabled"}`,
447
+ `- detected: ${openCodeSnapshot.capabilities.length ? openCodeSnapshot.capabilities.join(", ") : "none"}`,
448
+ `- session: ${openCodeSnapshot.sessionStatus}`,
449
+ `- current model: ${openCodeSnapshot.currentModel}`,
450
+ `- available models: ${openCodeSnapshot.availableModels.length}`,
451
+ "",
452
+ "Governance",
381
453
  `- permissions: ${config.permissionGuard ? "guarded" : "unguarded"}`,
454
+ `- context7: ${config.opencode.context7}`,
455
+ `- model profile: ${config.models.mode}`,
456
+ "",
457
+ "Package",
382
458
  `- package: ${doctor.packageReady ? "ready" : "unknown"}`,
383
459
  `- desktop config: ${doctor.desktopConfig ? "detected" : "not detected"}`,
384
460
  `- warnings: ${warnings.length === 0 ? "none" : warnings.join("; ")}`,
@@ -446,6 +522,9 @@ export function createLazyRuntime(ctx = {}) {
446
522
  formatCloseReport,
447
523
  formatInstallHealth,
448
524
  formatDoctorReport,
525
+ validateModelProfile,
526
+ log,
527
+ notify,
449
528
  getReferenceSnapshot: () => ({
450
529
  scope,
451
530
  workflow,
@@ -474,11 +553,25 @@ export function resolveLazyConfig(input, scope) {
474
553
  permissions: input?.opencode?.permissions ?? true,
475
554
  worktreeIsolation: input?.opencode?.worktreeIsolation ?? "risky-only",
476
555
  revertCheckpoints: input?.opencode?.revertCheckpoints ?? true,
556
+ context7: input?.opencode?.context7 ?? "suggest",
557
+ sdkControlPlane: input?.opencode?.sdkControlPlane ?? true,
558
+ sdkTelemetry: input?.opencode?.sdkTelemetry ?? true,
559
+ tuiNotifications: input?.opencode?.tuiNotifications ?? true,
477
560
  },
478
561
  closeReport: {
479
562
  autoCollect: input?.closeReport?.autoCollect ?? true,
480
563
  maxItems: input?.closeReport?.maxItems ?? 5,
481
564
  },
565
+ models: {
566
+ mode: input?.models?.mode ?? "preserve",
567
+ primary: input?.models?.primary,
568
+ defaultSubagent: input?.models?.defaultSubagent,
569
+ escalation: {
570
+ oracle: input?.models?.escalation?.oracle,
571
+ council: input?.models?.escalation?.council,
572
+ },
573
+ byAgent: input?.models?.byAgent ?? {},
574
+ },
482
575
  mode: input?.mode ?? "governor",
483
576
  maxSessionsPerAgent: input?.maxSessionsPerAgent ?? 2,
484
577
  maxActiveTaskDepth: input?.maxActiveTaskDepth ?? 4,
@@ -527,9 +620,14 @@ function createEmptyOpenCodeSnapshot(worktree) {
527
620
  pendingPermissions: 0,
528
621
  todos: 0,
529
622
  diffSummary: "not collected",
623
+ childSessions: 0,
624
+ changedFiles: 0,
530
625
  worktree,
531
626
  sessionStatus: "unknown",
627
+ currentModel: "OpenCode selected model",
628
+ availableModels: [],
532
629
  capabilities: [],
630
+ warnings: [],
533
631
  };
534
632
  }
535
633
  function createDoctorState(config) {
@@ -567,9 +665,14 @@ function normalizeOpenCodeSnapshot(input, worktree) {
567
665
  pendingPermissions: input?.pendingPermissions ?? 0,
568
666
  todos: input?.todos ?? 0,
569
667
  diffSummary: input?.diffSummary ?? "not collected",
668
+ childSessions: input?.childSessions ?? 0,
669
+ changedFiles: input?.changedFiles ?? 0,
570
670
  worktree: input?.worktree ?? worktree,
571
671
  sessionStatus: input?.sessionStatus ?? "unknown",
672
+ currentModel: input?.currentModel ?? "OpenCode selected model",
673
+ availableModels: input?.availableModels ?? [],
572
674
  capabilities: input?.capabilities ?? [],
675
+ warnings: input?.warnings ?? [],
573
676
  lastUpdatedAt: input?.lastUpdatedAt,
574
677
  };
575
678
  }
@@ -592,15 +695,46 @@ function formatTokenControl(stats) {
592
695
  "- job board mode: full when dirty, mini when clean",
593
696
  ].join("\n");
594
697
  }
698
+ function formatModelProfile(config) {
699
+ const primary = config.models.primary ?? "OpenCode selected model";
700
+ const subagent = config.models.defaultSubagent ?? "OpenCode selected model";
701
+ const oracle = config.models.escalation.oracle ?? config.models.primary ??
702
+ "OpenCode selected model";
703
+ const council = config.models.escalation.council ?? config.models.defaultSubagent ??
704
+ "OpenCode selected model";
705
+ const overrides = Object.keys(config.models.byAgent);
706
+ return [
707
+ "Model profile",
708
+ `- mode: ${config.models.mode}`,
709
+ `- primary: ${primary}`,
710
+ `- default subagent: ${subagent}`,
711
+ `- oracle: ${oracle}`,
712
+ `- council: ${council}`,
713
+ `- agent overrides: ${overrides.length > 0 ? overrides.join(", ") : "none"}`,
714
+ ].join("\n");
715
+ }
595
716
  function formatOpenCodeSnapshot(snapshot) {
717
+ // ponytail: 120s/600s thresholds, make configurable when users request it
718
+ const age = snapshot.lastUpdatedAt ? Date.now() - snapshot.lastUpdatedAt : Infinity;
719
+ const freshness = age < 120_000 ? "fresh" : age < 600_000 ? "stale" : "aged";
720
+ const time = snapshot.lastUpdatedAt
721
+ ? new Date(snapshot.lastUpdatedAt).toLocaleTimeString()
722
+ : "never";
723
+ const hasCapabilities = snapshot.capabilities.length > 0;
724
+ const degraded = hasCapabilities && snapshot.capabilities.includes("degraded");
596
725
  return [
597
726
  "OpenCode",
727
+ `- snapshot: ${freshness} @ ${time}`,
598
728
  `- session: ${snapshot.sessionStatus}`,
729
+ `- child sessions: ${snapshot.childSessions}`,
599
730
  `- pending permissions: ${snapshot.pendingPermissions}`,
600
731
  `- todos: ${snapshot.todos}`,
601
732
  `- diff: ${snapshot.diffSummary}`,
733
+ `- changed files: ${snapshot.changedFiles}`,
602
734
  `- worktree: ${snapshot.worktree}`,
603
- `- capabilities: ${snapshot.capabilities.length > 0 ? snapshot.capabilities.join(", ") : "not collected"}`,
735
+ `- model: ${snapshot.currentModel}`,
736
+ `- capabilities: ${hasCapabilities ? degraded ? "⚠ degraded" : snapshot.capabilities.join(", ") : "not collected"}`,
737
+ `- warnings: ${snapshot.warnings.length > 0 ? snapshot.warnings.join("; ") : "none"}`,
604
738
  ].join("\n");
605
739
  }
606
740
  function findUp(filename, startDir) {
@@ -11,7 +11,8 @@ You are a lazy workflow engine for coding work. Your job is to plan, schedule, d
11
11
  4. **build** — implement one issue at a time, test-first, YAGNI-gated. Load \`lazy/build\`.
12
12
  5. **review** — code review: find bugs, suggest deletions. Load \`lazy/review\`.
13
13
 
14
- - After every \`lazy/review\`: ask @lazy-oracle to identify deletions. No review is complete without a simplification pass.
14
+ - Escalate to @lazy-oracle only for high-risk decisions, ambiguous architecture,
15
+ persistent debugging failures, material review risk, or simplification judgment.
15
16
 
16
17
  ## Shortcuts
17
18
  - User says "just do it" → skip grill/specify/plan, go to build with ponytail rules.
@@ -35,7 +36,9 @@ You are a lazy workflow engine for coding work. Your job is to plan, schedule, d
35
36
  ## Available agents
36
37
  - @lazy-explorer — Fast codebase recon (glob, grep, AST). Delegate for discovery, not full content.
37
38
  - @lazy-librarian — External docs, API references, web research. Delegate for unfamiliar libraries.
38
- - @lazy-oracle — Architecture, risk, debugging strategy, code review. Delegate for high-stakes decisions, persistent bugs, simplification review.
39
+ - @lazy-oracle — Judgment-only advisor for architecture, risk, debugging strategy,
40
+ code review, and simplification. Delegate for high-stakes decisions; do not hand
41
+ it workflow ownership.
39
42
  - @lazy-designer — UI/UX design, visual polish, responsive layouts. Delegate for user-facing interfaces.
40
43
  - @lazy-fixer — Bounded implementation, fast execution. Delegate for well-defined, multi-file mechanical changes.
41
44
  - @lazy-observer — Visual analysis of images, screenshots, PDFs.
@@ -68,10 +71,6 @@ You are a lazy workflow engine for coding work. Your job is to plan, schedule, d
68
71
  - Don't summarize what you did unless asked.
69
72
  - Don't explain code unless asked.
70
73
  </Communication>`;
71
- const LAZY_SYSTEM_PROMPT_LITE = `## Lazy workflow (brief)
72
- If this is a non-trivial task, follow: grill → specify → plan → build → review.
73
- Available agents: @lazy-explorer, @lazy-oracle, @lazy-librarian, @lazy-designer, @lazy-fixer, @lazy-observer.
74
- Delegate parallel work when possible.`;
75
74
  // ponytail: inject PONYTAIL_MODE for ALL agents, LAZY_SYSTEM_PROMPT only for lazy primary.
76
75
  // Guard double-injection for both.
77
76
  export function createSystemTransformHook(runtime) {
@@ -91,13 +90,8 @@ export function createSystemTransformHook(runtime) {
91
90
  if (!sid)
92
91
  return;
93
92
  const agentName = runtime?.sessionAgentMap.get(sid);
94
- if (!agentName) {
95
- // Race condition: chat.params may not have run yet. Inject lite prompt.
96
- if (output.system.some((s) => s.includes("Lazy workflow")))
97
- return;
98
- output.system.push(LAZY_SYSTEM_PROMPT_LITE);
93
+ if (!agentName)
99
94
  return;
100
- }
101
95
  if (agentName !== "lazy")
102
96
  return;
103
97
  // Guard against double injection
package/dist/index.d.ts CHANGED
@@ -1,12 +1,13 @@
1
1
  import type { Plugin } from "@opencode-ai/plugin";
2
2
  /**
3
- * @lazyopencode/core — Governed team runtime for AI coding in OpenCode.
3
+ * lazyopencode-core — Governed team runtime for AI coding in OpenCode.
4
4
  *
5
5
  * One plugin. Zero config. Total takeover.
6
6
  *
7
- * Install: { "plugin": ["@lazyopencode/core"] }
7
+ * Install: { "plugin": ["lazyopencode-core"] }
8
8
  */
9
9
  declare const LazyOpenCodePluginV1: Plugin;
10
10
  declare const LazyOpenCodePlugin: Plugin;
11
+ export { LazyOpenCodeV2Plugin } from "./v2.js";
11
12
  export { LazyOpenCodePlugin, LazyOpenCodePluginV1 };
12
13
  export default LazyOpenCodePluginV1;