lazyopencode-core 0.0.3 → 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 +37 -4
- package/dist/hooks/runtime.js +123 -3
- 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;
|
|
@@ -92,13 +120,11 @@ export interface ContextStats {
|
|
|
92
120
|
totalPruned: number;
|
|
93
121
|
}
|
|
94
122
|
export type CloseEvidenceKind = "behavior" | "test" | "verification" | "risk" | "deletion";
|
|
95
|
-
export type EvidenceSource = "auto" | "manual";
|
|
96
123
|
export interface CloseReportState {
|
|
97
124
|
behaviorChanges: string[];
|
|
98
125
|
testRuns: Array<{
|
|
99
126
|
command: string;
|
|
100
127
|
result: "pass" | "fail" | "unknown";
|
|
101
|
-
source?: EvidenceSource;
|
|
102
128
|
}>;
|
|
103
129
|
lastTestCommand?: string;
|
|
104
130
|
lastVerifyCommand?: string;
|
|
@@ -111,11 +137,15 @@ export interface OpenCodeSnapshot {
|
|
|
111
137
|
pendingPermissions: number;
|
|
112
138
|
todos: number;
|
|
113
139
|
diffSummary: string;
|
|
140
|
+
childSessions: number;
|
|
141
|
+
changedFiles: number;
|
|
114
142
|
worktree: string;
|
|
115
143
|
sessionStatus: string;
|
|
144
|
+
currentModel: string;
|
|
145
|
+
availableModels: string[];
|
|
116
146
|
capabilities: string[];
|
|
147
|
+
warnings: string[];
|
|
117
148
|
lastUpdatedAt?: number;
|
|
118
|
-
errors?: string[];
|
|
119
149
|
}
|
|
120
150
|
export interface DoctorState {
|
|
121
151
|
v2Registration: boolean;
|
|
@@ -158,6 +188,9 @@ export interface LazyRuntime {
|
|
|
158
188
|
formatCloseReport(sessionID?: string): string;
|
|
159
189
|
formatInstallHealth(): string;
|
|
160
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>;
|
|
161
194
|
getReferenceSnapshot(): Record<string, unknown>;
|
|
162
195
|
}
|
|
163
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
|
}
|
|
@@ -200,7 +239,6 @@ export function createLazyRuntime(ctx = {}) {
|
|
|
200
239
|
closeReport.testRuns = appendLimited(closeReport.testRuns, {
|
|
201
240
|
command,
|
|
202
241
|
result: looksLikeFailure(text) ? "fail" : "pass",
|
|
203
|
-
source: "auto",
|
|
204
242
|
}, max);
|
|
205
243
|
closeReport.lastTestCommand = command;
|
|
206
244
|
}
|
|
@@ -244,7 +282,6 @@ export function createLazyRuntime(ctx = {}) {
|
|
|
244
282
|
closeReport.testRuns = appendLimited(closeReport.testRuns, {
|
|
245
283
|
command: text,
|
|
246
284
|
result: "unknown",
|
|
247
|
-
source: "manual",
|
|
248
285
|
}, max);
|
|
249
286
|
}
|
|
250
287
|
closeReport.updatedAt = Date.now();
|
|
@@ -267,6 +304,7 @@ export function createLazyRuntime(ctx = {}) {
|
|
|
267
304
|
}
|
|
268
305
|
lines.push("", formatInstallHealth());
|
|
269
306
|
lines.push("", formatTokenControl(contextStats));
|
|
307
|
+
lines.push("", formatModelProfile(config));
|
|
270
308
|
lines.push("", formatOpenCodeSnapshot(openCodeSnapshot));
|
|
271
309
|
const board = sessionID ? jobBoard.formatForPrompt(sessionID) : null;
|
|
272
310
|
lines.push("", board ?? "[Background Job Board]\n No jobs for this session.");
|
|
@@ -323,6 +361,11 @@ export function createLazyRuntime(ctx = {}) {
|
|
|
323
361
|
`- Terminal unreconciled jobs: ${terminal.length}`,
|
|
324
362
|
`- Reusable sessions: ${reusable.length}`,
|
|
325
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}`,
|
|
326
369
|
"",
|
|
327
370
|
"Changed behavior",
|
|
328
371
|
...formatStringList(closeReport.behaviorChanges),
|
|
@@ -346,6 +389,9 @@ export function createLazyRuntime(ctx = {}) {
|
|
|
346
389
|
`- council: ${config.council.enabled ? config.council.eligibility : "disabled"}`,
|
|
347
390
|
`- permission guard: ${config.permissionGuard ? "enabled" : "disabled"}`,
|
|
348
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"}`,
|
|
349
395
|
`- sdk: ${config.sdk.mode} + legacy hooks ${config.sdk.legacyHookAdapter ? "enabled" : "disabled"}`,
|
|
350
396
|
].join("\n");
|
|
351
397
|
};
|
|
@@ -378,13 +424,37 @@ export function createLazyRuntime(ctx = {}) {
|
|
|
378
424
|
if (config.council.enabled && config.council.eligibility === "always") {
|
|
379
425
|
warnings.push("council eligibility is always; guarded escalation is disabled");
|
|
380
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
|
+
}
|
|
381
436
|
return [
|
|
382
437
|
"LAZY DOCTOR",
|
|
438
|
+
"",
|
|
439
|
+
"Plugin registration",
|
|
383
440
|
`- v2 registration: ${doctor.v2Registration ? "ok" : "missing"}`,
|
|
384
441
|
`- legacy hooks: ${doctor.legacyHookAdapter ? "ok" : "disabled"}`,
|
|
385
442
|
`- skills: ${doctor.skills ? "ok" : "missing"}`,
|
|
386
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",
|
|
387
453
|
`- permissions: ${config.permissionGuard ? "guarded" : "unguarded"}`,
|
|
454
|
+
`- context7: ${config.opencode.context7}`,
|
|
455
|
+
`- model profile: ${config.models.mode}`,
|
|
456
|
+
"",
|
|
457
|
+
"Package",
|
|
388
458
|
`- package: ${doctor.packageReady ? "ready" : "unknown"}`,
|
|
389
459
|
`- desktop config: ${doctor.desktopConfig ? "detected" : "not detected"}`,
|
|
390
460
|
`- warnings: ${warnings.length === 0 ? "none" : warnings.join("; ")}`,
|
|
@@ -452,6 +522,9 @@ export function createLazyRuntime(ctx = {}) {
|
|
|
452
522
|
formatCloseReport,
|
|
453
523
|
formatInstallHealth,
|
|
454
524
|
formatDoctorReport,
|
|
525
|
+
validateModelProfile,
|
|
526
|
+
log,
|
|
527
|
+
notify,
|
|
455
528
|
getReferenceSnapshot: () => ({
|
|
456
529
|
scope,
|
|
457
530
|
workflow,
|
|
@@ -480,11 +553,25 @@ export function resolveLazyConfig(input, scope) {
|
|
|
480
553
|
permissions: input?.opencode?.permissions ?? true,
|
|
481
554
|
worktreeIsolation: input?.opencode?.worktreeIsolation ?? "risky-only",
|
|
482
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,
|
|
483
560
|
},
|
|
484
561
|
closeReport: {
|
|
485
562
|
autoCollect: input?.closeReport?.autoCollect ?? true,
|
|
486
563
|
maxItems: input?.closeReport?.maxItems ?? 5,
|
|
487
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
|
+
},
|
|
488
575
|
mode: input?.mode ?? "governor",
|
|
489
576
|
maxSessionsPerAgent: input?.maxSessionsPerAgent ?? 2,
|
|
490
577
|
maxActiveTaskDepth: input?.maxActiveTaskDepth ?? 4,
|
|
@@ -533,9 +620,14 @@ function createEmptyOpenCodeSnapshot(worktree) {
|
|
|
533
620
|
pendingPermissions: 0,
|
|
534
621
|
todos: 0,
|
|
535
622
|
diffSummary: "not collected",
|
|
623
|
+
childSessions: 0,
|
|
624
|
+
changedFiles: 0,
|
|
536
625
|
worktree,
|
|
537
626
|
sessionStatus: "unknown",
|
|
627
|
+
currentModel: "OpenCode selected model",
|
|
628
|
+
availableModels: [],
|
|
538
629
|
capabilities: [],
|
|
630
|
+
warnings: [],
|
|
539
631
|
};
|
|
540
632
|
}
|
|
541
633
|
function createDoctorState(config) {
|
|
@@ -573,9 +665,14 @@ function normalizeOpenCodeSnapshot(input, worktree) {
|
|
|
573
665
|
pendingPermissions: input?.pendingPermissions ?? 0,
|
|
574
666
|
todos: input?.todos ?? 0,
|
|
575
667
|
diffSummary: input?.diffSummary ?? "not collected",
|
|
668
|
+
childSessions: input?.childSessions ?? 0,
|
|
669
|
+
changedFiles: input?.changedFiles ?? 0,
|
|
576
670
|
worktree: input?.worktree ?? worktree,
|
|
577
671
|
sessionStatus: input?.sessionStatus ?? "unknown",
|
|
672
|
+
currentModel: input?.currentModel ?? "OpenCode selected model",
|
|
673
|
+
availableModels: input?.availableModels ?? [],
|
|
578
674
|
capabilities: input?.capabilities ?? [],
|
|
675
|
+
warnings: input?.warnings ?? [],
|
|
579
676
|
lastUpdatedAt: input?.lastUpdatedAt,
|
|
580
677
|
};
|
|
581
678
|
}
|
|
@@ -598,7 +695,26 @@ function formatTokenControl(stats) {
|
|
|
598
695
|
"- job board mode: full when dirty, mini when clean",
|
|
599
696
|
].join("\n");
|
|
600
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
|
+
}
|
|
601
716
|
function formatOpenCodeSnapshot(snapshot) {
|
|
717
|
+
// ponytail: 120s/600s thresholds, make configurable when users request it
|
|
602
718
|
const age = snapshot.lastUpdatedAt ? Date.now() - snapshot.lastUpdatedAt : Infinity;
|
|
603
719
|
const freshness = age < 120_000 ? "fresh" : age < 600_000 ? "stale" : "aged";
|
|
604
720
|
const time = snapshot.lastUpdatedAt
|
|
@@ -610,11 +726,15 @@ function formatOpenCodeSnapshot(snapshot) {
|
|
|
610
726
|
"OpenCode",
|
|
611
727
|
`- snapshot: ${freshness} @ ${time}`,
|
|
612
728
|
`- session: ${snapshot.sessionStatus}`,
|
|
729
|
+
`- child sessions: ${snapshot.childSessions}`,
|
|
613
730
|
`- pending permissions: ${snapshot.pendingPermissions}`,
|
|
614
731
|
`- todos: ${snapshot.todos}`,
|
|
615
732
|
`- diff: ${snapshot.diffSummary}`,
|
|
733
|
+
`- changed files: ${snapshot.changedFiles}`,
|
|
616
734
|
`- worktree: ${snapshot.worktree}`,
|
|
735
|
+
`- model: ${snapshot.currentModel}`,
|
|
617
736
|
`- capabilities: ${hasCapabilities ? degraded ? "⚠ degraded" : snapshot.capabilities.join(", ") : "not collected"}`,
|
|
737
|
+
`- warnings: ${snapshot.warnings.length > 0 ? snapshot.warnings.join("; ") : "none"}`,
|
|
618
738
|
].join("\n");
|
|
619
739
|
}
|
|
620
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;
|