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 +1 -1
- package/README.md +41 -5
- package/dist/agents/index.js +1 -1
- package/dist/agents/librarian.d.ts +1 -1
- package/dist/agents/librarian.js +2 -2
- package/dist/agents/oracle.d.ts +1 -1
- package/dist/agents/oracle.js +7 -0
- package/dist/hooks/lazy-command.js +21 -2
- package/dist/hooks/messages-transform.js +5 -2
- package/dist/hooks/runtime.d.ts +39 -1
- package/dist/hooks/runtime.js +136 -2
- package/dist/hooks/system-transform.js +6 -12
- package/dist/index.d.ts +3 -2
- package/dist/index.js +39 -16
- package/dist/opencode-control-plane.d.ts +16 -2
- package/dist/opencode-control-plane.js +241 -68
- package/dist/skills/lazy/debug/SKILL.md +1 -1
- package/dist/v2.js +1 -1
- package/docs/desktop-distribution.md +2 -2
- package/docs/opencode-integration.md +24 -6
- package/docs/positioning.md +2 -2
- package/docs/product-audit.md +4 -4
- package/docs/user-manual.md +36 -8
- package/docs/work-plan.md +5 -5
- package/package.json +2 -2
package/ATTRIBUTION.md
CHANGED
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
|
|
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 |
|
|
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
|
|
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.
|
|
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.
|
package/dist/agents/index.js
CHANGED
|
@@ -32,7 +32,7 @@ export function createAgents() {
|
|
|
32
32
|
},
|
|
33
33
|
"lazy-librarian": {
|
|
34
34
|
prompt: LIBRARIAN_PROMPT,
|
|
35
|
-
description: "External documentation, API references,
|
|
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.
|
|
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.";
|
package/dist/agents/librarian.js
CHANGED
|
@@ -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.
|
|
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
|
|
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
|
|
package/dist/agents/oracle.d.ts
CHANGED
|
@@ -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";
|
package/dist/agents/oracle.js
CHANGED
|
@@ -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
|
-
|
|
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,
|
|
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)
|
package/dist/hooks/runtime.d.ts
CHANGED
|
@@ -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 {
|
package/dist/hooks/runtime.js
CHANGED
|
@@ -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
|
-
`-
|
|
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
|
-
-
|
|
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 —
|
|
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
|
-
*
|
|
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": ["
|
|
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;
|