@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 +180 -56
- package/dist/agents.js +47 -240
- package/dist/commands.js +6 -6
- package/dist/index.js +145 -274
- package/dist/lacode.js +115 -27
- package/dist/pool.js +25 -2
- package/dist/tunnel.js +76 -25
- package/docs/CONTEXT.md +4 -4
- package/docs/TUNNEL.md +50 -8
- package/docs/USER-STORIES.md +41 -15
- package/package.json +4 -2
- package/presets/free-tier.json +1 -1
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
|
-
|
|
58
|
-
|
|
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
|
-
|
|
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
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
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
|
-
###
|
|
93
|
+
### If the agents are missing
|
|
72
94
|
|
|
73
|
-
A plugin that fails to
|
|
74
|
-
and
|
|
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
|
|
111
|
+
opencode agent list | grep -E '^(explore|implement|context)'
|
|
78
112
|
```
|
|
79
113
|
|
|
80
|
-
A working install prints `explore
|
|
81
|
-
|
|
82
|
-
grep returns nothing
|
|
83
|
-
|
|
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
|
|
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
|
|
194
|
-
exploration and implementation
|
|
195
|
-
|
|
196
|
-
|
|
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
|
|
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
|
|
217
|
-
"
|
|
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-
|
|
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
|
-
**
|
|
234
|
-
`
|
|
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
|
|
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
|
|
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
|
|