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/dist/index.js CHANGED
@@ -6,11 +6,11 @@ import { getSkillsDir } from "./skills/index.js";
6
6
  import { createCancelTaskTool, createCouncilTool } from "./tools/index.js";
7
7
  import { createOpenCodeControlPlane } from "./opencode-control-plane.js";
8
8
  /**
9
- * @lazyopencode/core — Governed team runtime for AI coding in OpenCode.
9
+ * lazyopencode-core — Governed team runtime for AI coding in OpenCode.
10
10
  *
11
11
  * One plugin. Zero config. Total takeover.
12
12
  *
13
- * Install: { "plugin": ["@lazyopencode/core"] }
13
+ * Install: { "plugin": ["lazyopencode-core"] }
14
14
  */
15
15
  const LazyOpenCodePluginV1 = async (ctx) => {
16
16
  const agents = createAgents();
@@ -19,7 +19,7 @@ const LazyOpenCodePluginV1 = async (ctx) => {
19
19
  directory: ctx.directory,
20
20
  worktree: ctx.worktree,
21
21
  });
22
- runtime.setControlPlane(createOpenCodeControlPlane(ctx.client));
22
+ runtime.setControlPlane(createOpenCodeControlPlane(ctx.client, ctx.directory));
23
23
  const hooks = createHooks(runtime);
24
24
  const councilTool = createCouncilTool(ctx.client, () => runtime.config.council, () => {
25
25
  const council = runtime.config.council;
@@ -46,7 +46,7 @@ const LazyOpenCodePluginV1 = async (ctx) => {
46
46
  const cfg = config;
47
47
  runtime.configure(cfg.lazyopencode);
48
48
  await runtime.load();
49
- cfg.agent = mergeAgents(cfg.agent ?? {}, agents);
49
+ cfg.agent = mergeAgents(cfg.agent ?? {}, agents, runtime.config);
50
50
  cfg.skills = cfg.skills || {};
51
51
  const paths = cfg.skills.paths || [];
52
52
  const skillsDir = getSkillsDir();
@@ -54,16 +54,7 @@ const LazyOpenCodePluginV1 = async (ctx) => {
54
54
  paths.push(skillsDir);
55
55
  }
56
56
  cfg.skills.paths = paths;
57
- // deno-lint-ignore no-explicit-any
58
- const mcp = cfg.mcp;
59
- if (!mcp?.context7) {
60
- // deno-lint-ignore no-explicit-any
61
- ;
62
- cfg.mcp = {
63
- ...(mcp || {}),
64
- context7: { command: ["npx", "-y", "@agentdesk/context7-mcp"] },
65
- };
66
- }
57
+ maybeInjectContext7(cfg, runtime.config);
67
58
  registerLazyCommands(cfg, runtime);
68
59
  },
69
60
  dispose: async () => {
@@ -73,12 +64,44 @@ const LazyOpenCodePluginV1 = async (ctx) => {
73
64
  };
74
65
  };
75
66
  const LazyOpenCodePlugin = LazyOpenCodePluginV1;
67
+ export { LazyOpenCodeV2Plugin } from "./v2.js";
76
68
  export { LazyOpenCodePlugin, LazyOpenCodePluginV1 };
77
69
  export default LazyOpenCodePluginV1;
78
- function mergeAgents(existing, lazyAgents) {
70
+ function mergeAgents(existing, lazyAgents, config) {
79
71
  const merged = { ...existing };
80
72
  for (const [name, defaults] of Object.entries(lazyAgents)) {
81
- merged[name] = { ...defaults, ...merged[name] };
73
+ const profileModel = modelForAgent(name, defaults, config);
74
+ const lazyDefaults = profileModel ? { ...defaults, model: profileModel } : defaults;
75
+ merged[name] = { ...lazyDefaults, ...merged[name] };
82
76
  }
83
77
  return merged;
84
78
  }
79
+ function modelForAgent(name, defaults, config) {
80
+ if (config.models.mode !== "profile")
81
+ return undefined;
82
+ if (config.models.byAgent[name])
83
+ return config.models.byAgent[name];
84
+ if (name === "lazy")
85
+ return config.models.primary;
86
+ if (name === "lazy-oracle")
87
+ return config.models.escalation.oracle ?? config.models.primary;
88
+ if (name === "lazy-councillor") {
89
+ return config.models.escalation.council ?? config.models.defaultSubagent;
90
+ }
91
+ if (defaults.mode === "subagent")
92
+ return config.models.defaultSubagent;
93
+ return undefined;
94
+ }
95
+ function maybeInjectContext7(cfg, config) {
96
+ if (config.opencode.context7 !== "inject")
97
+ return;
98
+ // deno-lint-ignore no-explicit-any
99
+ const mcp = cfg.mcp;
100
+ if (mcp?.context7)
101
+ return // deno-lint-ignore no-explicit-any
102
+ ;
103
+ cfg.mcp = {
104
+ ...(mcp || {}),
105
+ context7: { command: ["npx", "-y", "@agentdesk/context7-mcp"] },
106
+ };
107
+ }
@@ -1,20 +1,34 @@
1
+ export interface ModelProfileValidation {
2
+ currentModel: string;
3
+ availableModels: string[];
4
+ invalidModels: string[];
5
+ warnings: string[];
6
+ }
1
7
  export interface OpenCodeControlPlaneSnapshot {
2
8
  sessionStatus: string;
9
+ childSessions: number;
3
10
  pendingPermissions: number;
4
11
  todos: number;
5
12
  diffSummary: string;
13
+ changedFiles: number;
6
14
  worktree: string;
15
+ currentModel: string;
16
+ availableModels: string[];
7
17
  capabilities: string[];
18
+ warnings: string[];
8
19
  }
9
20
  export interface OpenCodeControlPlane {
10
21
  snapshot(sessionID?: string): Promise<OpenCodeControlPlaneSnapshot>;
22
+ validateModels(models: string[]): Promise<ModelProfileValidation>;
11
23
  wait(sessionID: string): Promise<{
12
24
  ok: boolean;
13
25
  reason?: string;
14
26
  }>;
15
- revert(checkpointID: string): Promise<{
27
+ revert(sessionID: string, messageID?: string): Promise<{
16
28
  ok: boolean;
17
29
  reason?: string;
18
30
  }>;
31
+ log(level: "debug" | "info" | "warn" | "error", message: string, metadata?: unknown): Promise<void>;
32
+ notify(kind: "info" | "warn" | "error", message: string): Promise<void>;
19
33
  }
20
- export declare function createOpenCodeControlPlane(client: unknown): OpenCodeControlPlane;
34
+ export declare function createOpenCodeControlPlane(client: unknown, directory?: string): OpenCodeControlPlane;
@@ -1,95 +1,268 @@
1
- export function createOpenCodeControlPlane(client) {
1
+ export function createOpenCodeControlPlane(client, directory) {
2
2
  const c = (client ?? {});
3
3
  return {
4
4
  async snapshot(sessionID) {
5
+ const warnings = [];
5
6
  const capabilities = detectCapabilities(c);
6
- const sessionStatus = await callString(c, ["sessionStatus", "status"], sessionID, "unknown");
7
- const pendingPermissions = await callCount(c, ["pendingPermissions", "permissions"], sessionID);
8
- const todos = await callCount(c, ["todos", "todo"], sessionID);
9
- const diffSummary = await callString(c, ["diffSummary", "diff"], sessionID, "not collected");
10
- const worktree = await callString(c, ["worktree", "projectWorktree"], sessionID, "unknown");
11
- return { sessionStatus, pendingPermissions, todos, diffSummary, worktree, capabilities };
7
+ const session = asRecord(c.session);
8
+ const v2Session = asRecord(asRecord(c.v2)?.session);
9
+ const v2Permission = asRecord(v2Session?.permission);
10
+ const status = await callSdk(session, "status", [{ directory }], warnings);
11
+ const sessionInfo = sessionID
12
+ ? await callSdk(session, "get", [{ sessionID, directory }], warnings)
13
+ : undefined;
14
+ const children = sessionID
15
+ ? await callSdk(session, "children", [{ sessionID, directory }], warnings)
16
+ : undefined;
17
+ const todos = sessionID
18
+ ? await callSdk(session, "todo", [{ sessionID, directory }], warnings)
19
+ : undefined;
20
+ const diff = sessionID
21
+ ? await callSdk(session, "diff", [{ sessionID, directory }], warnings)
22
+ : undefined;
23
+ const permissions = sessionID
24
+ ? await callSdk(v2Permission, "list", [{ sessionID }], warnings)
25
+ : undefined;
26
+ const providers = await getProviderModels(c, directory, warnings);
27
+ const fileStatus = await getFileStatus(c, directory, warnings);
28
+ return {
29
+ sessionStatus: extractSessionStatus(status, sessionInfo),
30
+ childSessions: countItems(children),
31
+ pendingPermissions: countItems(permissions),
32
+ todos: countItems(todos),
33
+ diffSummary: summarizeDiff(diff),
34
+ changedFiles: fileStatus.changedFiles,
35
+ worktree: extractWorktree(sessionInfo) ?? directory ?? "unknown",
36
+ currentModel: providers.currentModel,
37
+ availableModels: providers.availableModels,
38
+ capabilities,
39
+ warnings: unique(warnings),
40
+ };
41
+ },
42
+ async validateModels(models) {
43
+ const warnings = [];
44
+ const providers = await getProviderModels(c, directory, warnings);
45
+ const available = new Set(providers.availableModels);
46
+ const invalidModels = models
47
+ .filter(Boolean)
48
+ .filter((model) => !isProviderModel(model) || (available.size > 0 && !available.has(model)));
49
+ return {
50
+ currentModel: providers.currentModel,
51
+ availableModels: providers.availableModels,
52
+ invalidModels,
53
+ warnings: unique(warnings),
54
+ };
12
55
  },
13
56
  async wait(sessionID) {
14
- return await callOk(c, ["wait", "sessionWait"], sessionID);
57
+ const warnings = [];
58
+ const session = asRecord(c.session);
59
+ const v2Session = asRecord(asRecord(c.v2)?.session);
60
+ const value = await callSdk(v2Session, "wait", [{ sessionID }], warnings) ??
61
+ await callSdk(session, "wait", [{ sessionID, directory }], warnings);
62
+ if (value !== undefined)
63
+ return { ok: true };
64
+ return { ok: false, reason: warnings.join("; ") || "session.wait unavailable" };
65
+ },
66
+ async revert(sessionID, messageID) {
67
+ const warnings = [];
68
+ const session = asRecord(c.session);
69
+ const value = await callSdk(session, "revert", [{ sessionID, messageID, directory }], warnings);
70
+ if (value !== undefined)
71
+ return { ok: true };
72
+ return { ok: false, reason: warnings.join("; ") || "session.revert unavailable" };
73
+ },
74
+ async log(level, message, metadata) {
75
+ const app = asRecord(c.app);
76
+ const global = asRecord(c.global);
77
+ await callSdk(app, "log", [{ level, message, metadata }], []) ??
78
+ await callSdk(global, "log", [{ level, message, metadata }], []);
15
79
  },
16
- async revert(checkpointID) {
17
- return await callOk(c, ["revert", "revertCheckpoint"], checkpointID);
80
+ async notify(kind, message) {
81
+ const tui = asRecord(c.tui);
82
+ await callSdk(tui, "showToast", [{ type: kind, message }], []) ??
83
+ await callSdk(tui, "publish", [{ body: { type: "toast.show", variant: kind, message } }], []);
18
84
  },
19
85
  };
20
86
  }
21
87
  function detectCapabilities(client) {
22
- const names = [
23
- ["sessionStatus", "status"],
24
- ["children"],
25
- ["wait", "sessionWait"],
26
- ["context"],
27
- ["messages"],
28
- ["diffSummary", "diff"],
29
- ["todos", "todo"],
30
- ["pendingPermissions", "permissions"],
31
- ["worktree", "projectWorktree"],
32
- ["revert", "revertCheckpoint"],
88
+ const session = asRecord(client.session);
89
+ const v2 = asRecord(client.v2);
90
+ const v2Session = asRecord(v2?.session);
91
+ const v2Permission = asRecord(v2Session?.permission);
92
+ const fs = asRecord(v2?.fs);
93
+ const groups = [
94
+ ["session.status", session?.status],
95
+ ["session.get", session?.get],
96
+ ["session.children", session?.children],
97
+ ["session.todo", session?.todo],
98
+ ["session.diff", session?.diff],
99
+ ["session.messages", session?.messages],
100
+ ["session.wait", session?.wait ?? v2Session?.wait],
101
+ ["session.revert", session?.revert],
102
+ ["v2.session.context", v2Session?.context],
103
+ ["v2.session.permission", v2Permission?.list],
104
+ ["config.get", asRecord(client.config)?.get],
105
+ ["config.providers", asRecord(client.config)?.providers],
106
+ ["provider.list", asRecord(client.provider)?.list],
107
+ ["file.status", asRecord(client.file)?.status],
108
+ ["find.files", asRecord(client.find)?.files ?? fs?.find],
109
+ ["app.log", asRecord(client.app)?.log ?? asRecord(client.global)?.log],
110
+ ["tui.showToast", asRecord(client.tui)?.showToast],
33
111
  ];
34
- return names
35
- .filter((group) => group.some((name) => typeof client[name] === "function"))
36
- .map((group) => group[0]);
112
+ return groups.filter(([, fn]) => typeof fn === "function").map(([name]) => name);
37
113
  }
38
- async function callString(client, names, arg, fallback) {
114
+ async function getProviderModels(client, directory, warnings) {
115
+ const config = asRecord(client.config);
116
+ const provider = asRecord(client.provider);
117
+ const configData = await callSdk(config, "get", [{ directory }], warnings);
118
+ const providerData = await callSdk(config, "providers", [{ directory }], warnings) ??
119
+ await callSdk(provider, "list", [{ directory }], warnings);
120
+ return {
121
+ currentModel: extractCurrentModel(configData),
122
+ availableModels: extractAvailableModels(providerData),
123
+ };
124
+ }
125
+ async function getFileStatus(client, directory, warnings) {
126
+ const value = await callSdk(asRecord(client.file), "status", [{ directory }], warnings) ??
127
+ await callSdk(asRecord(client.vcs), "status", [{ directory }], warnings);
128
+ return { changedFiles: countItems(value) };
129
+ }
130
+ async function callSdk(target, name, args, warnings) {
131
+ const fn = target?.[name];
132
+ if (typeof fn !== "function")
133
+ return undefined;
39
134
  try {
40
- const value = await callFirst(client, names, arg);
41
- if (value === undefined || value === null)
42
- return fallback;
43
- if (typeof value === "string")
44
- return value;
45
- if (typeof value === "object") {
46
- const record = value;
47
- return String(record.summary ?? record.status ?? record.path ?? fallback);
135
+ const result = await fn(...args);
136
+ const unwrapped = unwrapResult(result);
137
+ if (isErrorResult(result)) {
138
+ warnings.push(`${name}: ${stringify(result.error)}`);
48
139
  }
49
- return String(value);
140
+ return unwrapped;
50
141
  }
51
- catch {
52
- return fallback;
142
+ catch (error) {
143
+ warnings.push(`${name}: ${error instanceof Error ? error.message : String(error)}`);
144
+ return undefined;
53
145
  }
54
146
  }
55
- async function callCount(client, names, arg) {
56
- try {
57
- const value = await callFirst(client, names, arg);
58
- if (Array.isArray(value))
59
- return value.length;
60
- if (typeof value === "number")
61
- return value;
62
- if (value && typeof value === "object") {
63
- const record = value;
64
- if (typeof record.count === "number")
65
- return record.count;
66
- if (Array.isArray(record.items))
67
- return record.items.length;
68
- }
147
+ function unwrapResult(value) {
148
+ if (value && typeof value === "object" && ("data" in value || "error" in value)) {
149
+ return value.data;
69
150
  }
70
- catch {
151
+ return value;
152
+ }
153
+ function isErrorResult(value) {
154
+ return Boolean(value && typeof value === "object" && "error" in value && value.error);
155
+ }
156
+ function asRecord(value) {
157
+ return value && typeof value === "object" ? value : undefined;
158
+ }
159
+ function countItems(value) {
160
+ const data = unwrapResult(value);
161
+ if (Array.isArray(data))
162
+ return data.length;
163
+ if (typeof data === "number")
164
+ return data;
165
+ const record = asRecord(data);
166
+ if (!record)
71
167
  return 0;
168
+ for (const key of ["count", "total"]) {
169
+ if (typeof record[key] === "number")
170
+ return record[key];
171
+ }
172
+ for (const key of ["items", "data", "children", "todos", "permissions", "requests", "files"]) {
173
+ if (Array.isArray(record[key]))
174
+ return record[key].length;
72
175
  }
73
- return 0;
176
+ return Object.keys(record).length;
74
177
  }
75
- async function callOk(client, names, arg) {
76
- try {
77
- const value = await callFirst(client, names, arg);
78
- if (value && typeof value === "object" && "ok" in value) {
79
- return value;
178
+ function extractSessionStatus(status, sessionInfo) {
179
+ const session = asRecord(sessionInfo);
180
+ const statusRecord = asRecord(status);
181
+ return String(session?.status ??
182
+ session?.state ??
183
+ statusRecord?.status ??
184
+ statusRecord?.state ??
185
+ (statusRecord && Object.keys(statusRecord).length > 0 ? "available" : "unknown"));
186
+ }
187
+ function summarizeDiff(value) {
188
+ const data = unwrapResult(value);
189
+ if (!data)
190
+ return "not collected";
191
+ if (typeof data === "string")
192
+ return data || "empty";
193
+ const record = asRecord(data);
194
+ if (!record)
195
+ return String(data);
196
+ const summary = record.summary ?? record.text ?? record.diff;
197
+ if (typeof summary === "string" && summary.trim())
198
+ return summary;
199
+ const changed = countItems(record);
200
+ return changed > 0 ? `${changed} changed file(s)` : "empty";
201
+ }
202
+ function extractWorktree(value) {
203
+ const record = asRecord(value);
204
+ return String(record?.worktree ?? record?.directory ?? record?.path ?? record?.location ?? "") ||
205
+ undefined;
206
+ }
207
+ function extractCurrentModel(value) {
208
+ const data = asRecord(unwrapResult(value));
209
+ const raw = data?.model ?? data?.default_model ?? data?.small_model;
210
+ if (typeof raw === "string")
211
+ return raw;
212
+ const model = asRecord(raw);
213
+ if (model)
214
+ return joinModel(model.providerID, model.modelID ?? model.id);
215
+ return "OpenCode selected model";
216
+ }
217
+ function extractAvailableModels(value) {
218
+ const data = unwrapResult(value);
219
+ const providers = Array.isArray(data)
220
+ ? data
221
+ : asRecord(data)?.providers ?? asRecord(data)?.items ?? asRecord(data)?.data;
222
+ if (!Array.isArray(providers))
223
+ return [];
224
+ const models = [];
225
+ for (const provider of providers) {
226
+ const p = asRecord(provider);
227
+ const providerID = String(p?.id ?? p?.providerID ?? p?.name ?? "");
228
+ const modelList = p?.models;
229
+ if (Array.isArray(modelList)) {
230
+ for (const model of modelList) {
231
+ const m = asRecord(model);
232
+ const modelID = String(m?.id ?? m?.modelID ?? m?.name ?? "");
233
+ const joined = joinModel(providerID, modelID);
234
+ if (joined)
235
+ models.push(joined);
236
+ }
237
+ }
238
+ else if (modelList && typeof modelList === "object") {
239
+ for (const modelID of Object.keys(modelList)) {
240
+ const joined = joinModel(providerID, modelID);
241
+ if (joined)
242
+ models.push(joined);
243
+ }
80
244
  }
81
- return { ok: true };
82
- }
83
- catch (error) {
84
- return { ok: false, reason: error instanceof Error ? error.message : String(error) };
85
245
  }
246
+ return unique(models);
86
247
  }
87
- async function callFirst(client, names, arg) {
88
- for (const name of names) {
89
- const fn = client[name];
90
- if (typeof fn === "function") {
91
- return await fn(arg);
92
- }
248
+ function joinModel(providerID, modelID) {
249
+ const provider = String(providerID ?? "");
250
+ const model = String(modelID ?? "");
251
+ return provider && model ? `${provider}/${model}` : "";
252
+ }
253
+ function isProviderModel(value) {
254
+ return /^[^/\s]+\/[^/\s]+$/.test(value);
255
+ }
256
+ function stringify(value) {
257
+ if (typeof value === "string")
258
+ return value;
259
+ try {
260
+ return JSON.stringify(value);
93
261
  }
94
- return undefined;
262
+ catch {
263
+ return String(value);
264
+ }
265
+ }
266
+ function unique(items) {
267
+ return Array.from(new Set(items.filter(Boolean)));
95
268
  }
@@ -6,7 +6,7 @@ description: Systematic diagnosis loop for bugs where the cause is unclear.
6
6
  ## Process
7
7
 
8
8
  1. **Reproduce.** Can you reliably trigger it? Gather exact steps/logs. If unreproducible, document environment/conditions.
9
- 2. **Isolate.** Narrow scope: what changed (git log/bisect)? Smallest triggering input? Boundary? If bug involves a library API, check current docs via context7.
9
+ 2. **Isolate.** Narrow scope: what changed (git log/bisect)? Smallest triggering input? Boundary? If bug involves a library API, check current docs via configured documentation tools.
10
10
  3. **Hypothesize.** Form exactly one hypothesis. State it clearly.
11
11
  4. **Test hypothesis.** Smallest test/log that proves/disproves it. Do NOT fix yet.
12
12
  5. **Iterate.** Disproven → new hypothesis. Proven → go to 6. Stuck after 3 cycles → escalate to @lazy-oracle.
package/dist/v2.js CHANGED
@@ -2,7 +2,7 @@ import { define } from "@opencode-ai/plugin/v2/promise";
2
2
  import { createAgents } from "./agents/index.js";
3
3
  import { getSkillsDir } from "./skills/index.js";
4
4
  export const LazyOpenCodeV2Plugin = define({
5
- id: "@lazyopencode/core",
5
+ id: "lazyopencode-core",
6
6
  setup(context) {
7
7
  const ctx = context;
8
8
  ctx.agent?.transform((draft) => {
@@ -9,7 +9,7 @@ LazyOpenCode Desktop ships the governed defaults and health surface.
9
9
  ## Strategy
10
10
 
11
11
  The plugin remains the source of truth. Desktop bundles and enables
12
- `@lazyopencode/core`; it does not duplicate LazyOpenCode governance logic.
12
+ `lazyopencode-core`; it does not duplicate LazyOpenCode governance logic.
13
13
 
14
14
  ## First-Run Defaults
15
15
 
@@ -17,7 +17,7 @@ Desktop should merge the defaults from
17
17
  `apps/lazyopencode-desktop/lazyopencode.default.jsonc` into the user's generated
18
18
  OpenCode config:
19
19
 
20
- - Add `@lazyopencode/core` to `plugin` if absent.
20
+ - Add `lazyopencode-core` to `plugin` if absent.
21
21
  - Add `lazyopencode` defaults only where the user has not set values.
22
22
  - Preserve provider, auth, model, MCP, session, and project settings.
23
23
 
@@ -3,12 +3,11 @@
3
3
  LazyOpenCode is an OpenCode-native workflow governor. The plugin should work
4
4
  with no `lazyopencode` config block; configuration only customizes defaults.
5
5
 
6
- OpenCode loads `dist/index.js` from the npm package. The default export uses the
7
- v2 promise registration surface for agents, commands, skills, and references.
8
- The named `LazyOpenCodePluginV1` export remains available for legacy hook
9
- registration and existing tests. The legacy adapter stays enabled by default
10
- because current governance still depends on chat, message, command, permission,
11
- and tool hooks.
6
+ OpenCode loads `dist/index.js` from the npm package. The default export remains
7
+ the legacy hook adapter because current governance depends on chat, message,
8
+ command, permission, and tool hooks. The named `LazyOpenCodeV2Plugin` export
9
+ keeps the v2 promise registration surface available for agents, commands,
10
+ skills, and references while v2 hook coverage matures.
12
11
 
13
12
  ## Hook Boundaries
14
13
 
@@ -22,6 +21,22 @@ and tool hooks.
22
21
  - chat transforms: inject workflow guidance, job-board status, token-control
23
22
  pruning, image redirects, and Ponytail behavior.
24
23
 
24
+ ## SDK Control Plane
25
+
26
+ `0.0.4` uses the OpenCode SDK as a best-effort control plane for status, doctor,
27
+ and close reports. The adapter prefers real SDK groups such as `session`,
28
+ `config`, `file`, `find`, `app`, `tui`, and v2 session permission/fs APIs.
29
+
30
+ Collected evidence includes:
31
+
32
+ - session status, child sessions, todos, messages, diffs, wait, and revert
33
+ - pending session permissions from v2 permission APIs when available
34
+ - configured providers and models for model profile validation
35
+ - file status / changed file counts
36
+ - app logging and TUI notifications when available
37
+
38
+ Missing SDK APIs degrade to warnings. They should never block normal governance.
39
+
25
40
  ## Zero Config
26
41
 
27
42
  Outside Desktop, users still install/load the plugin through OpenCode's normal
@@ -36,6 +51,9 @@ plugin mechanism. Once loaded, LazyOpenCode defaults are complete:
36
51
  - `sdk.legacyHookAdapter: true`
37
52
  - `takeover: "governed"`
38
53
  - `opencode.worktreeIsolation: "risky-only"`
54
+ - `opencode.sdkControlPlane: true`
55
+ - `opencode.sdkTelemetry: true`
56
+ - `opencode.tuiNotifications: true`
39
57
  - `closeReport.autoCollect: true`
40
58
 
41
59
  ## Config Merge Contract
@@ -39,6 +39,6 @@ Autonomous coding assistants optimize for longer independent runs, memory, and
39
39
  self-improvement loops. LazyOpenCode emphasizes governed OpenCode operation:
40
40
  small scope, explicit risk gates, budget visibility, and boring closure.
41
41
 
42
- Desktop is a later `0.1.0` task. The current `0.0.1` target is to make
43
- `@lazyopencode/core` a complete plugin kernel that a Desktop fork can preinstall
42
+ Desktop is a later `0.1.0` task. The current `0.0.x` target is to make
43
+ `lazyopencode-core` a complete plugin kernel that a Desktop fork can preinstall
44
44
  safely.
@@ -11,7 +11,7 @@ prompt toolbox. The product combines three things:
11
11
  - A governance layer for scope, risk, budget, permissions, and closure.
12
12
  - A future Desktop distribution that makes those defaults visible and easy.
13
13
 
14
- The current `0.0.1` core is close to a usable kernel: commands, agents, skills,
14
+ The current `0.0.x` core is close to a usable kernel: commands, agents, skills,
15
15
  runtime state, job board, council guard, token control, doctor output, and close
16
16
  report all exist and pass verification. The next work is mostly deepening,
17
17
  simplifying, and making the boundaries sharper.
@@ -73,7 +73,7 @@ Small scope, visible work, bounded context, reviewed output.
73
73
  - Worktree isolation is advice/policy, not full automation.
74
74
  - Doctor output checks the important basics, but should become a deeper module
75
75
  with structured checks.
76
- - Desktop is intentionally not implemented in `0.0.1`.
76
+ - Desktop is intentionally not implemented in `0.0.x`.
77
77
 
78
78
  ## Project Design
79
79
 
@@ -171,12 +171,12 @@ but adds governance and closure.
171
171
  Compared with autonomous coding assistants, LazyOpenCode stays OpenCode-native
172
172
  and prioritizes controlled engineering workflow over long independent runs.
173
173
 
174
- Compared with future Desktop distributions, `@lazyopencode/core` is the source
174
+ Compared with future Desktop distributions, `lazyopencode-core` is the source
175
175
  of truth. Desktop should package and visualize core behavior, not duplicate it.
176
176
 
177
177
  ## Release Readiness
178
178
 
179
- `0.0.1` is releaseable once these stay true:
179
+ `0.0.4` is releaseable once these stay true:
180
180
 
181
181
  - `npm run verify` passes.
182
182
  - README and docs use governed team runtime positioning.