@lawaty/lacode 1.1.0 → 1.3.0-beta.0

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/CHANGELOG.md CHANGED
@@ -7,6 +7,89 @@ All notable changes to LaCode are recorded here. The format follows
7
7
  Versions come from git release commits; the npm version is bumped in the same
8
8
  commit as the changelog entry it describes.
9
9
 
10
+ ## [1.3.0-beta.0] — provider readiness, timeline roots, and a reason on cancelled tasks
11
+
12
+ ### Added
13
+
14
+ - **The pool gates slots on provider readiness.** Before routing, LaCode asks
15
+ opencode which providers are connected and which carry credentials, then
16
+ excludes slots whose provider is unconfigured or unauthenticated. Readiness
17
+ fails open until the first successful probe, so an offline start still routes;
18
+ `pool_status` names the provider to log in to.
19
+ - **`/reprocess-timeline` rebuilds the root session.** A reprocess started from a
20
+ subagent walks the `parentID` chain (with cycle protection) and summarizes the
21
+ root session, instead of only the child it was invoked from.
22
+ - **Host sync merges a Cloudflare Workers AI provider.** `bin/oc-sync` reads
23
+ `CLOUDFLARE_ACCOUNT_ID` (or derives it from the local provider URL) and merges
24
+ the provider into each host's `opencode.jsonc` after the auth sync; existing
25
+ providers are preserved and the write is backed up.
26
+ - **`OC_AUTH_PROVIDERS` can come from the env file.** The post-publish host sync
27
+ falls back to `~/.config/lacode/env` when the variable is unset.
28
+
29
+ ### Changed
30
+
31
+ - **A pool-cancelled subtask now carries its cause.** The bare `Task cancelled`
32
+ card is rewritten in place to `Task Cancelled by LaCode: <cause>` — slot,
33
+ model, elapsed time, failure kind and claim state. The old warning toast is
34
+ gone; the reason lives on the task itself. The rewrite is best-effort: if
35
+ opencode rejects the part update, a warning is logged and the original text
36
+ stays.
37
+ - **A model that disappears is benched, not retried forever.** A terminal
38
+ model-not-found error cools the model for the cap and releases the claim.
39
+
40
+ ### Fixed
41
+
42
+ - **Startup no longer waits on the provider endpoint.** The config hook used to
43
+ await `client.provider.list` while opencode was still bootstrapping the same
44
+ instance, which deadlocked startup. The first spawn now refreshes readiness
45
+ before routing, and `pool_status`/`pool_recheck` still force a refresh.
46
+ - **A terminal session error, or an abort that does not confirm, releases the
47
+ claim** instead of holding it until the reaper.
48
+
49
+ ## [1.2.0] — voice, a per-session timeline, and a pool without a cap
50
+
51
+ ### Added
52
+
53
+ - **Voice control for the TUI.** `lacode-voice` runs a local daemon that captures
54
+ audio, transcribes it through Groq, and forwards prompts to the TUI over a
55
+ loopback-only control socket. Wake-word detection is optional: the Vosk and
56
+ Porcupine engines, the recorder and the audio capture path each degrade
57
+ gracefully when their native dependency, key or device is missing. The daemon,
58
+ transport and audio code live in `src/voice/`; the TUI is published as the
59
+ `./tui` export.
60
+ - **A per-session timeline.** `src/plugins/timeline.ts` summarizes a session's
61
+ transcript into an append-only JSONL file under
62
+ `~/.local/share/opencode/lacode/timeline/`, and `/reprocess-timeline` rebuilds
63
+ it from the full transcript through the `timeline_reprocess` tool. Internal
64
+ summarizer sessions stay out of notifications.
65
+ - **`npm run score`** probes every cost-zero model the configured providers
66
+ serve, scores context, output, speed and reliability, and suggests a pool
67
+ order. Results are written to `~/.config/lacode/model-scores.json`.
68
+
69
+ ### Changed
70
+
71
+ - **BREAKING: Slot routing is one pass, and the four-slot cap is gone.** A
72
+ slot's `weight` is now its cap, not half of a two-pass budget: the pool fills
73
+ each slot to its weight in declaration order in one pass, then overflows to the
74
+ least-loaded slot. Every line is a slot, and a model may repeat — each line
75
+ adds seats at its position. A preset tuned for the old two-pass behaviour sees
76
+ roughly half the concurrency it did before; to restore it, double the weights
77
+ or list a model again.
78
+ - **The pool hot-reloads a bucket-only edit.** Changing only a slot's `b:` bucket
79
+ now counts as a change and re-validates the table.
80
+ - **A stated `Retry-After` is honoured up to six hours**, instead of being
81
+ clamped to the fifteen-minute backoff cap.
82
+
83
+ ### Fixed
84
+
85
+ - **A voice daemon shutdown waits for the transcription in flight**, and manual
86
+ recording is bounded at two minutes instead of growing without limit.
87
+ - **The voice control socket stays on loopback** unless a non-local bind is
88
+ explicitly opted into, and the wake engine treats an empty environment value
89
+ as unset.
90
+ - **The timeline strips terminal control sequences** before rendering model
91
+ output, and shares its session state across a double-loaded plugin.
92
+
10
93
  ## [1.1.0] — a map contract, feedback, and host credential sync
11
94
 
12
95
  ### Added
@@ -875,7 +958,10 @@ experimental; `latest` remains 0.4.2.
875
958
 
876
959
  - First published release: pooled subagents over a shared set of models.
877
960
 
878
- [Unreleased]: https://github.com/lawaty/lacode/compare/v1.0.0...HEAD
961
+ [Unreleased]: https://github.com/lawaty/lacode/compare/v1.2.0...HEAD
962
+ [1.3.0-beta.0]: https://github.com/lawaty/lacode/releases/tag/v1.3.0-beta.0
963
+ [1.2.0]: https://github.com/lawaty/lacode/compare/v1.1.0...v1.2.0
964
+ [1.1.0]: https://github.com/lawaty/lacode/compare/v1.0.0...v1.1.0
879
965
  [1.0.0]: https://github.com/lawaty/lacode/compare/v0.11.0...v1.0.0
880
966
  [0.11.0]: https://github.com/lawaty/lacode/compare/v0.10.0...v0.11.0
881
967
  [0.10.0]: https://github.com/lawaty/lacode/compare/v0.9.0...v0.10.0
package/README.md CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  > Ultimate opencode setup for Go users
4
4
 
5
- > LaCode 1.0.0 ships on the npm **`latest`** dist-tag. Prereleases use
5
+ > LaCode 1.2.0 ships on the npm **`latest`** dist-tag. Prereleases use
6
6
  > `beta`: install `@lawaty/lacode@beta`, or pin an exact version.
7
7
 
8
8
  LaCode is an opencode plugin. It routes delegated work onto a weighted pool of
@@ -145,7 +145,7 @@ option is optional:
145
145
  | Option | Default | Meaning |
146
146
  | --- | --- | --- |
147
147
  | `enabled` | `true` | The single off switch. `false` injects nothing at all and does no writes. |
148
- | `models` | — | An inline slot list. Highest-priority source. |
148
+ | `models` | — | An inline slot list; `weight` is that line's budget and cap, a model may repeat, and there is no slot cap. Highest-priority source. |
149
149
  | `modelsFile` | — | An explicit slot file; short-circuits the [resolution chain](docs/CONFIGURATION.md#pool-preset-resolution). |
150
150
  | `reapMs` | `30000` | Hang-reaper interval. |
151
151
  | `probeMs` | `900000` | How often benched models are checked by actually using them. `0` turns the periodic check off and leaves the `pool_recheck` tool as the only way to ask. |
@@ -170,7 +170,7 @@ The running session keeps its loaded code; the new version takes effect on the
170
170
  next start, and the log says so. Run `lacode update` to do it on demand, or
171
171
  `lacode update --check` to just report.
172
172
 
173
- 1.0.0 is published to the `latest` dist-tag; prereleases use `beta`. This check
173
+ 1.2.0 is published to the `latest` dist-tag; prereleases use `beta`. This check
174
174
  follows `latest`. A beta install is therefore reported as current even when a
175
175
  newer beta exists,
176
176
  and `lacode update` will not move it: pin the version you want in the `plugin`
@@ -186,7 +186,7 @@ lacode version
186
186
  ```
187
187
 
188
188
  ```
189
- version 1.0.0
189
+ version 1.2.0
190
190
  root /home/you/lacode
191
191
  source checkout
192
192
  commit <hash> (dirty)
@@ -213,11 +213,17 @@ When it reports `stale`, rebuild with `npm run build`. No network is touched;
213
213
  ## What you get
214
214
 
215
215
  Three pooled agents: `explore`, `implement` and `context`. You spawn the base
216
- name and the pool picks a cheap model slot for it. Behind each base sits up to
217
- four hidden variants — one per slot — and a spawn of the bare name is the only
218
- thing that works: a hand-named variant such as `explore-1` is coerced to its
219
- base and routed by the pool, so naming one cannot pin a provider or skip
220
- ordered slot filling.
216
+ name and the pool picks a cheap model slot for it. Behind each base sits one
217
+ hidden variant per slot — however many the pool declares — and a spawn of the
218
+ bare name is the only thing that works: a hand-named variant such as `explore-1`
219
+ is coerced to its base and routed by the pool, so naming one cannot pin a
220
+ provider or skip ordered slot filling.
221
+
222
+ A slot's `weight` is both its concurrency budget and its cap: the pool fills
223
+ slots in declaration order in one pass, then overflows to the least-loaded slot.
224
+ There is no slot cap, and a model may appear on more than one line — each line
225
+ is a separate slot with its own weight — so to give a model more capacity, list
226
+ it again.
221
227
 
222
228
  A model that rate-limits or runs out of quota is benched until its window ends,
223
229
  so one sick provider does not take the pool with it.
@@ -235,9 +241,9 @@ serve at all is benched for the quota cap and named in the report.
235
241
  A change is reported as a toast and a log line; a pass that finds nothing changed says
236
242
  nothing at all.
237
243
 
238
- Nine commands: `/context-init`, `/context-update`, `/context-review`,
239
- `/lacode-doctor`, `/agent-instructions`, `/pool`, `/pool-recheck`, `/image-in-cb`
240
- and `/image`. Everything is added **only when your config does not already
244
+ Ten commands: `/context-init`, `/context-update`, `/context-review`,
245
+ `/lacode-doctor`, `/agent-instructions`, `/pool`, `/pool-recheck`,
246
+ `/reprocess-timeline`, `/image-in-cb` and `/image`. Everything is added **only when your config does not already
241
247
  define it** — an agent or command you wrote yourself is left byte-identical,
242
248
  never patched or partially merged. `{"enabled": false}` turns the whole thing off
243
249
  without leaving a trace. The exact injection list, and the five keys that are
@@ -391,6 +397,12 @@ to emit a number they cannot derive — an unpriced model reports `unknown`, nev
391
397
  `0`. How the two modes differ is in [docs/BENCHMARKS.md](docs/BENCHMARKS.md),
392
398
  which explains the harness and deliberately contains no results.
393
399
 
400
+ `npm run score` is the other measurement: it probes each configured provider's
401
+ free models with one tiny request and ranks them by measured latency, context and
402
+ output limit. It prints a suggested `pool.txt` but never edits it, and writes the
403
+ raw rows to `~/.config/lacode/model-scores.json`. Run it every few days — free
404
+ models and quota windows move.
405
+
394
406
  ## Remote hosts
395
407
 
396
408
  Notifications raised on a remote host need a way back to your desktop. The
@@ -445,6 +457,7 @@ want it.
445
457
  npm run build # esbuild -> dist/ (what the tarball ships)
446
458
  npm test # node --test tests/ — offline, no network, no running server
447
459
  npm run benchmark # the cost harness; see docs/BENCHMARKS.md
460
+ npm run score # rank free models by context, output and measured latency
448
461
  ```
449
462
 
450
463
  Node ≥ 22.18.
package/dist/agents.js CHANGED
@@ -25,15 +25,16 @@ var PACKAGE_ROOT = findPackageRoot(dirname(fileURLToPath(import.meta.url)));
25
25
 
26
26
  // src/lib/pool.ts
27
27
  var CLAIM_TTL_MS = 10 * 60 * 1e3;
28
+ var UNBOUND_CLAIM_MS = 2 * 60 * 1e3;
28
29
  var DEAD_FILE_MS = 2 * CLAIM_TTL_MS;
29
30
  var ORPHAN_TMP_MS = 60 * 1e3;
30
31
  var CATALOG_TTL_MS = 10 * 60 * 1e3;
32
+ var PROVIDER_LIST_TTL_MS = 60 * 1e3;
31
33
  var STUCK_MIN_AGE_MS = 5 * 60 * 1e3;
32
34
  var STUCK_IDLE_MS = 3 * 60 * 1e3;
33
35
  var MAX_CLAIM_AGE_MS = 30 * 60 * 1e3;
34
36
  var HANG_COOLDOWN_MS = 10 * 60 * 1e3;
35
37
  var UNAVAILABLE_COOLDOWN_MS = 60 * 1e3;
36
- var MAX_SLOTS = 4;
37
38
  var COOLDOWN_BASE_MS = 60 * 1e3;
38
39
  var COOLDOWN_MAX_MS = 15 * 60 * 1e3;
39
40
  var QUOTA_COOLDOWN_BASE_MS = 15 * 60 * 1e3;
@@ -44,6 +45,12 @@ var DEFAULT_PRESET = join2(PRESETS_DIR, "free-tier.json");
44
45
  var USER_CONFIG_DIR = join2(homedir(), ".config", "lacode");
45
46
  var USER_MODELS_PATH = join2(USER_CONFIG_DIR, "pool.json");
46
47
  var USER_MODELS_TXT = join2(USER_CONFIG_DIR, "pool.txt");
48
+ var FALLBACK_SLOTS = [
49
+ { index: 1, model: "opencode/big-pickle", weight: 3 },
50
+ { index: 2, model: "opencode-go/beam-free", weight: 2 },
51
+ { index: 3, model: "opencode/deepseek-v4.1-flash-free", weight: 1 },
52
+ { index: 4, model: "opencode/glm-5.3-flash-free", weight: 1 }
53
+ ];
47
54
  var STATE_SWEEP_INTERVAL_MS = 60 * 60 * 1e3;
48
55
  var CHILD_MEMORY_MS = 30 * 6e4;
49
56
 
@@ -977,12 +984,12 @@ Report briefly what you changed and where. Do not edit any other file.`,
977
984
  }
978
985
  }
979
986
  };
980
- function buildAgentSet() {
987
+ function buildAgentSet(slotCount = FALLBACK_SLOTS.length) {
981
988
  const set = { ...AGENTS };
982
989
  for (const base of POOLED_BASES) {
983
990
  const def = AGENTS[base];
984
991
  if (!def) continue;
985
- for (let slot = 1; slot <= MAX_SLOTS; slot++) {
992
+ for (let slot = 1; slot <= Math.max(1, slotCount); slot++) {
986
993
  const variant = {
987
994
  ...def,
988
995
  description: `Hidden pool slot ${slot} for ${base}. Its model is NOT set here -- LaCode injects it from the resolved pool preset at config time, so the preset is the only place a pool model is written down. Spawn '${base}' and let the pool route.`,
package/dist/commands.js CHANGED
@@ -29,16 +29,17 @@ var PACKAGE_ROOT = findPackageRoot(dirname(fileURLToPath(import.meta.url)));
29
29
 
30
30
  // src/lib/pool.ts
31
31
  var CLAIM_TTL_MS = 10 * 60 * 1e3;
32
+ var UNBOUND_CLAIM_MS = 2 * 60 * 1e3;
32
33
  var DEAD_FILE_MS = 2 * CLAIM_TTL_MS;
33
34
  var ORPHAN_TMP_MS = 60 * 1e3;
34
35
  var CATALOG_TTL_MS = 10 * 60 * 1e3;
36
+ var PROVIDER_LIST_TTL_MS = 60 * 1e3;
35
37
  var STUCK_MIN_AGE_MS = 5 * 60 * 1e3;
36
38
  var STUCK_IDLE_MS = 3 * 60 * 1e3;
37
39
  var MAX_CLAIM_AGE_MS = 30 * 60 * 1e3;
38
40
  var HANG_COOLDOWN_MS = 10 * 60 * 1e3;
39
41
  var UNAVAILABLE_COOLDOWN_MS = 60 * 1e3;
40
42
  var MAX_WEIGHT = 100;
41
- var MAX_SLOTS = 4;
42
43
  var COOLDOWN_BASE_MS = 60 * 1e3;
43
44
  var COOLDOWN_MAX_MS = 15 * 60 * 1e3;
44
45
  var QUOTA_COOLDOWN_BASE_MS = 15 * 60 * 1e3;
@@ -158,7 +159,7 @@ Follow the rules for the file in your system prompt, then report what you change
158
159
  Print one PASS/FAIL line per check below, with the exact fix for any FAIL.
159
160
 
160
161
  1. Pool status \u2014 call the \`pool_status\` tool. Report the slot list, each
161
- slot's load against its ceiling (twice the slot's weight), and which models
162
+ slot's load against its budget (its weight), and which models
162
163
  are cooling.
163
164
 
164
165
  2. OmniRoute configured \u2014 inspect the \`@lawaty/lacode\` plugin options in the
@@ -195,10 +196,10 @@ The pool is ONE global slot table shared by every pooled base (explore, implemen
195
196
 
196
197
  2. Resolve the instruction above into a target slot table. These are the rules the loader enforces \u2014 never write a file that breaks one:
197
198
  - one slot per line: \`<provider>/<model-id> [weight]\`
198
- - at most ${MAX_SLOTS} slots, and a model may appear only once
199
- - weight is optional (default 1) and must be a number in (0, ${MAX_WEIGHT}]. It is the slot's active-session budget for one pass \u2014 how many turns it takes per cycle \u2014 and its concurrency ceiling is twice that. NOT a priority; pick order follows line order.
199
+ - any number of slots; a model may appear on more than one line, and each line is its own slot with its own budget
200
+ - weight is optional (default 1) and must be a number in (0, ${MAX_WEIGHT}]. It is the slot's active-session budget \u2014 how many concurrent sessions it may hold. The pool's total capacity is the sum of the weights. NOT a priority; pick order follows line order.
200
201
  - \`#\` starts a comment (whole-line or trailing); blank lines are ignored.
201
- If the instruction names a model ambiguously, or would need more than ${MAX_SLOTS} slots, stop and ask rather than guessing.
202
+ If the instruction names a model ambiguously, stop and ask rather than guessing.
202
203
 
203
204
  3. Write the WHOLE file, not a patch. This file is an override, not an edit target: any slot you drop disappears, and slot numbers are line positions, so a partial write silently renumbers slots. Keep the existing comment header. If the write fails on a missing directory, create \`~/.config/lacode\` first and retry.
204
205
 
@@ -224,6 +225,11 @@ $ARGUMENTS
224
225
 
225
226
  Report the tool's output as it came back, then add one short line naming what changed: re-admitted, still limited, or benched because the provider does not serve the model. If a model was benched as \`model-missing\`, say so and name the replacement in \`~/.config/lacode/pool.txt\` \u2014 but change nothing yourself.`
226
227
  },
228
+ "reprocess-timeline": {
229
+ description: "Rebuild this session's timeline from its full transcript",
230
+ agent: "implement",
231
+ template: "Call the `timeline_reprocess` tool exactly once with no arguments. Then reply with only its output."
232
+ },
227
233
  "image-in-cb": {
228
234
  description: "Attach the clipboard image to this message \u2014 macOS needs pngpaste, Linux needs wl-paste or xclip.",
229
235
  template: "$ARGUMENTS"