@lawaty/lacode 0.1.0 → 0.1.2

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/README.md CHANGED
@@ -39,6 +39,12 @@ directory to create. Do not run `npm i -g @lawaty/lacode`: a global npm install
39
39
  is never consulted by opencode, so it changes nothing about whether the plugin
40
40
  loads.
41
41
 
42
+ It is also the form to keep as your default: if your opencode does not load the
43
+ bare specifier, add to this line rather than replacing it. The
44
+ [procedure below](#if-the-agents-are-missing) says when that is, and
45
+ [which versions this is known on](#which-opencode-versions-this-is-known-on)
46
+ says what is actually verified.
47
+
42
48
  To install it explicitly instead — or to install it once for every project on
43
49
  the machine rather than per config — use the CLI, which does the same thing:
44
50
 
@@ -53,37 +59,177 @@ agent set will look stale for no visible reason.
53
59
 
54
60
  ### Where it is installed
55
61
 
56
- opencode installs npm plugins itself, into one directory per specifier under
57
- its cache — not into your project's `node_modules`, and not from anywhere a
58
- global npm install put them:
62
+ opencode installs npm plugins itself, into one directory per specifier under its
63
+ cache — not into your project's `node_modules`, and not from anywhere a global
64
+ npm install put them. Ask opencode where that cache is instead of assuming,
65
+ because where it lives differs by platform:
66
+
67
+ ```bash
68
+ opencode debug paths
69
+ ```
70
+
71
+ It prints the directories opencode is using on this machine — config, data, log
72
+ and the rest. Find the **cache** line and append
73
+ `/packages/<specifier>/node_modules/` to the path it reports. On Linux with no
74
+ `XDG_CACHE_HOME` set, that comes out as, for example:
59
75
 
60
76
  ```
61
- $XDG_CACHE_HOME/opencode/packages/<specifier>/node_modules/
62
- # ~/.cache/opencode/packages/@lawaty/lacode@latest/node_modules/ by default
77
+ ~/.cache/opencode/packages/@lawaty/lacode@latest/node_modules/
63
78
  ```
64
79
 
65
- `$XDG_CACHE_HOME` defaults to `~/.cache`; set it if you keep a cache elsewhere.
66
- To inspect what a specifier actually resolved to, list its directory under
67
- `~/.cache/opencode/packages/`. An empty directory with no `node_modules` inside
68
- it means the install failed — almost always an unpublished or misspelled package
69
- — and opencode starts normally **without** the plugin.
80
+ **That last line is an illustration of one platform's layout, not a path to
81
+ copy.** Where the same cache root sits on another operating system is not
82
+ something this document should guess at — read it from `opencode debug paths`
83
+ and use what it prints.
84
+
85
+ To inspect what a specifier actually resolved to, list its directory under that
86
+ cache root. An **empty** directory with no `node_modules` inside it means the
87
+ install failed — almost always an unpublished or misspelled package — and
88
+ opencode starts normally **without** the plugin. A **populated** directory there
89
+ means the install succeeded and tells you nothing at all about whether the
90
+ plugin was loaded; that is the next section, and telling those two apart is the
91
+ whole reason for reading it.
70
92
 
71
- ### Verifying the install
93
+ ### If the agents are missing
72
94
 
73
- A plugin that fails to install is silent: there is no error, opencode starts,
74
- and it simply does not have the agents. Check for them:
95
+ A plugin that fails to load is silent: opencode starts normally, prints nothing,
96
+ logs nothing, and the agents are simply not there. Work down this. Every step
97
+ below is written to hold whichever opencode version you are running — none of
98
+ them depends on knowing the answer in advance.
99
+
100
+ **1 — Write the bare specifier and restart.**
101
+
102
+ ```json
103
+ { "plugin": ["@lawaty/lacode"] }
104
+ ```
105
+
106
+ Nothing here hot-reloads, so the restart is part of the step, not a footnote.
107
+
108
+ **2 — Check whether the plugin loaded.**
75
109
 
76
110
  ```bash
77
- opencode agent list | grep -E '^(explore-fast|implement-fast|explore-deep|implement-deep)'
111
+ opencode agent list | grep -E '^(explore|implement|context)'
78
112
  ```
79
113
 
80
- A working install prints `explore-fast`, `implement-fast`, `explore-deep` and
81
- `implement-deep`, each with the hidden `-1`..`-4` pool variants alongside. If the
82
- grep returns nothing but opencode's built-ins, the plugin did not load: check the
83
- cache directory above, and check that `plugin` is in the config file opencode
84
- actually reads. Run this in the same shell environment opencode runs in — an
114
+ A working install prints `explore`, `implement` and `context`, each with the
115
+ hidden `-1`..`-4` pool variants alongside. If the
116
+ grep returns nothing and the list holds only opencode's own built-ins, the plugin
117
+ did not load. Run it in the same shell environment opencode runs in — an
85
118
  `agent list` under a different `HOME` or `XDG_CONFIG_HOME` tests a different
86
- machine.
119
+ machine. **If the agents are listed, stop here: the one line above is the whole
120
+ install**, and nothing further in this section applies to you.
121
+
122
+ **3 — Decide which failure you are looking at.** Inspect the package directory
123
+ under the cache root from
124
+ [Where it is installed](#where-it-is-installed), and also check that `plugin`
125
+ really is in the config file opencode reads.
126
+
127
+ - **Empty** (no `node_modules` inside): the install failed — check the package
128
+ name, and that it is published. There is nothing to load yet.
129
+ - **Populated**: the install worked and the loader dropped the plugin. Go to
130
+ step 4. These two look identical from the outside, which is why this step
131
+ exists.
132
+
133
+ **4 — If the install worked and the agents are still missing, point the loader
134
+ at the built file.** Add a second entry; leave the bare name in place:
135
+
136
+ ```json
137
+ {
138
+ "plugin": [
139
+ "@lawaty/lacode",
140
+ "file:///ABSOLUTE-PATH-FROM-debug-paths/packages/@lawaty/lacode@latest/node_modules/@lawaty/lacode/dist/index.js"
141
+ ]
142
+ }
143
+ ```
144
+
145
+ `ABSOLUTE-PATH-FROM-debug-paths` is a placeholder to be replaced, not a
146
+ directory: take the cache path `opencode debug paths` reports for **your**
147
+ machine and put it in front of `/packages/…`. It must be an absolute path with
148
+ `$HOME` already expanded and no `~` left in it. Re-derive it after a cache wipe
149
+ or a reinstall — the directory is keyed by specifier, so it moves when the
150
+ version does.
151
+
152
+ Why keep both entries instead of swapping the bare name out:
153
+
154
+ - The bare name is what makes opencode install the package into its cache, and
155
+ therefore what creates the path the `file://` entry points at.
156
+ - The `file://` entry is what actually loads the plugin.
157
+ - They **cannot double-inject** on opencode 1.18.35, where the bare name
158
+ contributes nothing at all: the pair loads the plugin exactly once. On any
159
+ other version this is untested, which is precisely why step 4 is something you
160
+ take *because* step 2 showed the agents missing, and never on the expectation
161
+ that it would help.
162
+
163
+ **5 — Restart, and expect two restarts after a cache wipe.** The first start
164
+ installs the package and creates the path the `file://` entry points at, but a
165
+ path that does not exist at startup is a path that cannot be loaded in that same
166
+ run. The second start finds it and loads the plugin. After that it is steady
167
+ state.
168
+
169
+ This package also publishes `main` and an `exports["./server"]` entry pointing at
170
+ the same built file, because those are two of the shapes a plugin loader
171
+ resolving an entry point consults. They are forward-compatibility metadata and
172
+ they cost nothing — but they do **not** fix this on 1.18.35: adding either one to
173
+ the *installed* cache copy was tested there and the bare specifier still loaded
174
+ nothing. No other version has been observed where they help either. Use the
175
+ `file://` entry.
176
+
177
+ ### Which opencode versions this is known on
178
+
179
+ Exactly one version has been measured, and the procedure above is deliberately
180
+ not built around that fact. Treat this table as the evidence it is, which is one
181
+ row wide.
182
+
183
+ | opencode version | bare spec loads? | `file://` needed? | evidence |
184
+ | --- | --- | --- | --- |
185
+ | 1.18.35 | No — installs the package, then silently drops it | Yes | **Verified** on 1.18.35: a populated cache directory, zero agents injected, no error and no log line; the `file://` entry loads the full agent set. |
186
+ | Any other version | **Unknown** | **Unknown** | **Untested.** No other opencode version has been run against this package in either direction. |
187
+
188
+ Every command quoted on this page was run against 1.18.35. **If your version is
189
+ not the one measured here, follow the procedure above rather than reading this
190
+ table as coverage of your build**: each step stands on its own, and step 2 tells
191
+ you which situation you are in before any version-specific advice is relevant.
192
+
193
+ ### macOS
194
+
195
+ The plugin runs on macOS. It is plain JavaScript with no native dependency, and
196
+ what the package publishes is the built JavaScript rather than TypeScript
197
+ sources, so nothing about loading it is Linux-only. Use the procedure above
198
+ unchanged.
199
+
200
+ Three things differ, and all three are worth reading rather than assuming:
201
+
202
+ - **Paths.** Do not copy a cache path out of this document — the example under
203
+ [Where it is installed](#where-it-is-installed) is one platform's layout. Run
204
+ `opencode debug paths` and build the `file://` entry from what it reports.
205
+ This page deliberately does not name a macOS cache root: where opencode keeps
206
+ it on a Mac has not been established here, and a plausible-looking guess is
207
+ worse than an instruction to look.
208
+ - **Notifications** are delivered through `osascript`, or through
209
+ `terminal-notifier` when `osascript` is not installed, rather than through the
210
+ Linux `notify-send` (US-26). `lacode doctor` reports which channel your machine
211
+ would actually use.
212
+ - **The repository's shell scripts are Linux-only by design**, and are not in the
213
+ published package. They drive one person's SSH topology, so each is on the side
214
+ it belongs to:
215
+
216
+ | Script | Runs on | Why it is Linux-only |
217
+ | --- | --- | --- |
218
+ | `bin/oc`, `bin/oc-notify` | the **remote host** that runs opencode | Server-side. They are pushed to `~/bin/` on each host by `bin/oc-sync` and notify *your desktop* over the tunnel. |
219
+ | `bin/oc-notify-receiver` | your **desktop** | Delivers through `notify-send` (libnotify), and the persistent forward that reaches it is a systemd user unit. |
220
+ | `bin/oc-sync` | your **desktop** | Installs those systemd user units and runs `loginctl enable-linger`. |
221
+
222
+ None of this is a limitation of the package: a macOS client installs the plugin
223
+ and uses the `lacode` CLI, and both are portable. The scripts above are the
224
+ author's personal remote-tunnel tooling. `bin/oc-sync` refuses to run where it
225
+ cannot work and says which `lacode` subcommand to use instead, rather than
226
+ failing somewhere less obvious.
227
+
228
+ **The test suite runs on macOS.** It is written to pass on a Mac as well as on
229
+ Linux, so "it works on a Mac" is a supported claim rather than an intention. The
230
+ few checks that exercise the Linux-only shell scripts above skip themselves on a
231
+ machine that cannot run them, naming the reason — a bash 3.2 with no `mapfile`,
232
+ or no GNU `timeout` — rather than passing silently or failing confusingly.
87
233
 
88
234
  ### The `lacode` CLI
89
235
 
@@ -138,7 +284,6 @@ option is optional:
138
284
  | `enabled` | `true` | The single off switch. `false` injects nothing at all and does no writes. |
139
285
  | `models` | — | An inline slot list. Highest-priority source. |
140
286
  | `modelsFile` | — | An explicit slot file; short-circuits the chain below. |
141
- | `deepModel` | — | A `provider/model-id` pinned onto `explore-deep` and `implement-deep`. |
142
287
  | `reapMs` | `30000` | Hang-reaper interval. |
143
288
 
144
289
  ### Pool preset resolution
@@ -162,46 +307,26 @@ that is a sensible default, not because the pool requires it.
162
307
  ### Which models run which agent
163
308
 
164
309
  Only the **pool** writes a model onto a pooled agent, from whichever source wins
165
- above. The pooled bases (`explore-fast`, `implement-fast`, `context-manager`)
310
+ above. The pooled bases (`explore`, `implement`, `context`)
166
311
  and their hidden `-1`..`-4` variants therefore carry no model of their own: a
167
312
  model written next to an agent would be a second source of truth that silently
168
313
  wins whenever routing falls through. Spawn a base and let the pool route it.
169
314
 
170
- `explore-deep` and `implement-deep` are **not** pooled — they are the deliberate
171
- escalation above a pooled tier — so nothing routes them and nothing writes their
172
- model. Left alone they fall through to your default model, which turns "I am
173
- stuck, escalate" into an unannounced jump to the most expensive model in your
174
- setup. Set `deepModel` to pin them:
175
-
176
- ```jsonc
177
- "plugin": [["@lawaty/lacode", { "deepModel": "your-provider/your-deep-model" }]]
178
- ```
179
-
180
- It is an option and not a literal in the package because which model to escalate
181
- onto is a property of your providers, not of LaCode. If you keep a `whitelist`
182
- on that provider, LaCode appends the pinned model to it for you: the whitelist is
183
- an allowlist, so opencode drops any model a provider offers that it does not
184
- name, and an unlisted pin would resolve to nothing at all. If you have no
185
- whitelist there, none is written — an absent one already allows every model. With
186
- `deepModel` unset, no model is written and the deep tiers use your
187
- default model — the behaviour of earlier versions.
188
-
189
315
  ### ⚠️ Per-agent tool grants must be in your own config
190
316
 
191
317
  **LaCode cannot grant tools to its agents, and this is not an oversight you can
192
318
  work around by configuring it.** If you keep a global `"tools"` block in your
193
- config — for example `"tools": { "playwright_*": false }` — then the four
194
- exploration and implementation tiers (`explore-fast`, `explore-deep`,
195
- `implement-fast`, `implement-deep`, plus their hidden `-1`..`-4` pool variants)
196
- lose browser access, and **nothing tells them why**: the tool call is simply
197
- refused mid-task. `context-manager` gets no grant either, which is correct — it
319
+ config — for example `"tools": { "playwright_*": false }` — then the pooled
320
+ exploration and implementation bases (`explore` and `implement`, plus their
321
+ hidden `-1`..`-4` pool variants) lose browser access, and **nothing tells them why**: the tool call is simply
322
+ refused mid-task. `context` gets no grant either, which is correct — it
198
323
  writes the context map and has no business in a browser.
199
324
 
200
325
  opencode folds an agent's `tools` into its permission list **at config-load
201
326
  time**, while decoding the config — before any plugin runs. A plugin's `config`
202
327
  hook mutates the already-decoded config, so the fold never re-runs and anything
203
328
  it puts in `agent.tools` is inert. (Verified on opencode 1.18.34/1.18.35 with
204
- `opencode debug agent explore-fast`: a grant written in your own config shows two
329
+ `opencode debug agent explore`: a grant written in your own config shows two
205
330
  `playwright_*` entries — `deny` then `allow`, last one winning — while the same
206
331
  grant injected by the plugin shows only the `deny`.) LaCode's other injected
207
332
  fields are not affected; `permission`, `prompt`, `model`, `description`,
@@ -213,15 +338,13 @@ fields are not affected; `permission`, `prompt`, `model`, `description`,
213
338
  {
214
339
  "tools": { "playwright_*": false },
215
340
  "agent": {
216
- "explore-fast": { "tools": { "playwright_*": true } },
217
- "explore-deep": { "tools": { "playwright_*": true } },
218
- "implement-fast": { "tools": { "playwright_*": true } },
219
- "implement-deep": { "tools": { "playwright_*": true } }
341
+ "explore": { "tools": { "playwright_*": true } },
342
+ "implement": { "tools": { "playwright_*": true } }
220
343
  }
221
344
  }
222
345
  ```
223
346
 
224
- The hidden pool variants (`explore-fast-1`, `implement-fast-2`, …) inherit
347
+ The hidden pool variants (`explore-1`, `implement-2`, …) inherit
225
348
  nothing here — add a line per variant you actually spawn, or drop the global
226
349
  deny and scope it to the agents that should not have browser access instead.
227
350
 
@@ -230,8 +353,8 @@ process.
230
353
 
231
354
  ### What gets injected
232
355
 
233
- **19 agent keys**: the 7 bases `build`, `plan`, `explore-fast`, `explore-deep`,
234
- `implement-fast`, `implement-deep`, `context-manager`, plus one hidden
356
+ **17 agent keys**: the 5 bases `build`, `plan`, `explore`, `implement`,
357
+ `context`, plus one hidden
235
358
  `<base>-1`..`-4` pool variant for each of the 3 pooled bases.
236
359
 
237
360
  `build` and `plan` exist in opencode as built-ins, so on a stock config those
@@ -278,7 +401,7 @@ inventories: it answers "what should I look at first?" and nothing else, which
278
401
  is what makes reading it cheaper than the exploration it replaces.
279
402
 
280
403
  It keeps itself current. When a session that edited code goes idle, LaCode
281
- starts a `context-manager` run that borrows a slot from the same pool every
404
+ starts a `context` run that borrows a slot from the same pool every
282
405
  other subagent uses and updates the map from the changed paths and their
283
406
  architectural neighbourhood — defaulting to *no edit*, since a changed file is
284
407
  not a changed fact. Only that one agent ever writes the map; everything else
@@ -407,7 +530,8 @@ they call the same `scripts/build.mjs` the tarball does.
407
530
  is the guard: it reads the `### US-N:` headings out of `docs/USER-STORIES.md`
408
531
  and the `[US-N]` markers out of the test names and fails if they disagree, in
409
532
  either direction. **A test that covers a story must have the id at the start of
410
- its name** — `test("[US-7] a paid model is accepted")` — or CI's guard fails it.
533
+ its name** — `test("[US-7] a paid model is accepted")` — or the coverage guard
534
+ fails it.
411
535
  A story with no test is as much a failure as a test naming a story that does not
412
536
  exist.
413
537