@lawaty/lacode 1.1.0 → 1.2.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 +47 -1
- package/README.md +22 -9
- package/dist/agents.js +8 -3
- package/dist/commands.js +9 -5
- package/dist/index.js +1017 -94
- package/dist/lacode-voice.js +2351 -0
- package/dist/pool.js +73 -38
- package/dist/tui.js +1282 -0
- package/docs/CONFIGURATION.md +17 -7
- package/docs/USER-STORIES.md +11 -11
- package/package.json +12 -4
- package/presets/README.md +17 -19
- package/presets/free-tier.json +11 -13
- package/scripts/score-models.mjs +353 -0
package/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,50 @@ 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.2.0] — voice, a per-session timeline, and a pool without a cap
|
|
11
|
+
|
|
12
|
+
### Added
|
|
13
|
+
|
|
14
|
+
- **Voice control for the TUI.** `lacode-voice` runs a local daemon that captures
|
|
15
|
+
audio, transcribes it through Groq, and forwards prompts to the TUI over a
|
|
16
|
+
loopback-only control socket. Wake-word detection is optional: the Vosk and
|
|
17
|
+
Porcupine engines, the recorder and the audio capture path each degrade
|
|
18
|
+
gracefully when their native dependency, key or device is missing. The daemon,
|
|
19
|
+
transport and audio code live in `src/voice/`; the TUI is published as the
|
|
20
|
+
`./tui` export.
|
|
21
|
+
- **A per-session timeline.** `src/plugins/timeline.ts` summarizes a session's
|
|
22
|
+
transcript into an append-only JSONL file under
|
|
23
|
+
`~/.local/share/opencode/lacode/timeline/`, and `/reprocess-timeline` rebuilds
|
|
24
|
+
it from the full transcript through the `timeline_reprocess` tool. Internal
|
|
25
|
+
summarizer sessions stay out of notifications.
|
|
26
|
+
- **`npm run score`** probes every cost-zero model the configured providers
|
|
27
|
+
serve, scores context, output, speed and reliability, and suggests a pool
|
|
28
|
+
order. Results are written to `~/.config/lacode/model-scores.json`.
|
|
29
|
+
|
|
30
|
+
### Changed
|
|
31
|
+
|
|
32
|
+
- **BREAKING: Slot routing is one pass, and the four-slot cap is gone.** A
|
|
33
|
+
slot's `weight` is now its cap, not half of a two-pass budget: the pool fills
|
|
34
|
+
each slot to its weight in declaration order in one pass, then overflows to the
|
|
35
|
+
least-loaded slot. Every line is a slot, and a model may repeat — each line
|
|
36
|
+
adds seats at its position. A preset tuned for the old two-pass behaviour sees
|
|
37
|
+
roughly half the concurrency it did before; to restore it, double the weights
|
|
38
|
+
or list a model again.
|
|
39
|
+
- **The pool hot-reloads a bucket-only edit.** Changing only a slot's `b:` bucket
|
|
40
|
+
now counts as a change and re-validates the table.
|
|
41
|
+
- **A stated `Retry-After` is honoured up to six hours**, instead of being
|
|
42
|
+
clamped to the fifteen-minute backoff cap.
|
|
43
|
+
|
|
44
|
+
### Fixed
|
|
45
|
+
|
|
46
|
+
- **A voice daemon shutdown waits for the transcription in flight**, and manual
|
|
47
|
+
recording is bounded at two minutes instead of growing without limit.
|
|
48
|
+
- **The voice control socket stays on loopback** unless a non-local bind is
|
|
49
|
+
explicitly opted into, and the wake engine treats an empty environment value
|
|
50
|
+
as unset.
|
|
51
|
+
- **The timeline strips terminal control sequences** before rendering model
|
|
52
|
+
output, and shares its session state across a double-loaded plugin.
|
|
53
|
+
|
|
10
54
|
## [1.1.0] — a map contract, feedback, and host credential sync
|
|
11
55
|
|
|
12
56
|
### Added
|
|
@@ -875,7 +919,9 @@ experimental; `latest` remains 0.4.2.
|
|
|
875
919
|
|
|
876
920
|
- First published release: pooled subagents over a shared set of models.
|
|
877
921
|
|
|
878
|
-
[Unreleased]: https://github.com/lawaty/lacode/compare/v1.
|
|
922
|
+
[Unreleased]: https://github.com/lawaty/lacode/compare/v1.2.0...HEAD
|
|
923
|
+
[1.2.0]: https://github.com/lawaty/lacode/compare/v1.1.0...v1.2.0
|
|
924
|
+
[1.1.0]: https://github.com/lawaty/lacode/compare/v1.0.0...v1.1.0
|
|
879
925
|
[1.0.0]: https://github.com/lawaty/lacode/compare/v0.11.0...v1.0.0
|
|
880
926
|
[0.11.0]: https://github.com/lawaty/lacode/compare/v0.10.0...v0.11.0
|
|
881
927
|
[0.10.0]: https://github.com/lawaty/lacode/compare/v0.9.0...v0.10.0
|
package/README.md
CHANGED
|
@@ -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. |
|
|
@@ -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
|
|
217
|
-
|
|
218
|
-
thing that works: a hand-named variant such as `explore-1`
|
|
219
|
-
base and routed by the pool, so naming one cannot pin a
|
|
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
|
-
|
|
239
|
-
`/lacode-doctor`, `/agent-instructions`, `/pool`, `/pool-recheck`,
|
|
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
|
@@ -33,7 +33,6 @@ var STUCK_IDLE_MS = 3 * 60 * 1e3;
|
|
|
33
33
|
var MAX_CLAIM_AGE_MS = 30 * 60 * 1e3;
|
|
34
34
|
var HANG_COOLDOWN_MS = 10 * 60 * 1e3;
|
|
35
35
|
var UNAVAILABLE_COOLDOWN_MS = 60 * 1e3;
|
|
36
|
-
var MAX_SLOTS = 4;
|
|
37
36
|
var COOLDOWN_BASE_MS = 60 * 1e3;
|
|
38
37
|
var COOLDOWN_MAX_MS = 15 * 60 * 1e3;
|
|
39
38
|
var QUOTA_COOLDOWN_BASE_MS = 15 * 60 * 1e3;
|
|
@@ -44,6 +43,12 @@ var DEFAULT_PRESET = join2(PRESETS_DIR, "free-tier.json");
|
|
|
44
43
|
var USER_CONFIG_DIR = join2(homedir(), ".config", "lacode");
|
|
45
44
|
var USER_MODELS_PATH = join2(USER_CONFIG_DIR, "pool.json");
|
|
46
45
|
var USER_MODELS_TXT = join2(USER_CONFIG_DIR, "pool.txt");
|
|
46
|
+
var FALLBACK_SLOTS = [
|
|
47
|
+
{ index: 1, model: "opencode/big-pickle", weight: 3 },
|
|
48
|
+
{ index: 2, model: "opencode-go/beam-free", weight: 2 },
|
|
49
|
+
{ index: 3, model: "opencode/deepseek-v4.1-flash-free", weight: 1 },
|
|
50
|
+
{ index: 4, model: "opencode/glm-5.3-flash-free", weight: 1 }
|
|
51
|
+
];
|
|
47
52
|
var STATE_SWEEP_INTERVAL_MS = 60 * 60 * 1e3;
|
|
48
53
|
var CHILD_MEMORY_MS = 30 * 6e4;
|
|
49
54
|
|
|
@@ -977,12 +982,12 @@ Report briefly what you changed and where. Do not edit any other file.`,
|
|
|
977
982
|
}
|
|
978
983
|
}
|
|
979
984
|
};
|
|
980
|
-
function buildAgentSet() {
|
|
985
|
+
function buildAgentSet(slotCount = FALLBACK_SLOTS.length) {
|
|
981
986
|
const set = { ...AGENTS };
|
|
982
987
|
for (const base of POOLED_BASES) {
|
|
983
988
|
const def = AGENTS[base];
|
|
984
989
|
if (!def) continue;
|
|
985
|
-
for (let slot = 1; slot <=
|
|
990
|
+
for (let slot = 1; slot <= Math.max(1, slotCount); slot++) {
|
|
986
991
|
const variant = {
|
|
987
992
|
...def,
|
|
988
993
|
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
|
@@ -38,7 +38,6 @@ var MAX_CLAIM_AGE_MS = 30 * 60 * 1e3;
|
|
|
38
38
|
var HANG_COOLDOWN_MS = 10 * 60 * 1e3;
|
|
39
39
|
var UNAVAILABLE_COOLDOWN_MS = 60 * 1e3;
|
|
40
40
|
var MAX_WEIGHT = 100;
|
|
41
|
-
var MAX_SLOTS = 4;
|
|
42
41
|
var COOLDOWN_BASE_MS = 60 * 1e3;
|
|
43
42
|
var COOLDOWN_MAX_MS = 15 * 60 * 1e3;
|
|
44
43
|
var QUOTA_COOLDOWN_BASE_MS = 15 * 60 * 1e3;
|
|
@@ -158,7 +157,7 @@ Follow the rules for the file in your system prompt, then report what you change
|
|
|
158
157
|
Print one PASS/FAIL line per check below, with the exact fix for any FAIL.
|
|
159
158
|
|
|
160
159
|
1. Pool status \u2014 call the \`pool_status\` tool. Report the slot list, each
|
|
161
|
-
slot's load against its
|
|
160
|
+
slot's load against its budget (its weight), and which models
|
|
162
161
|
are cooling.
|
|
163
162
|
|
|
164
163
|
2. OmniRoute configured \u2014 inspect the \`@lawaty/lacode\` plugin options in the
|
|
@@ -195,10 +194,10 @@ The pool is ONE global slot table shared by every pooled base (explore, implemen
|
|
|
195
194
|
|
|
196
195
|
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
196
|
- one slot per line: \`<provider>/<model-id> [weight]\`
|
|
198
|
-
-
|
|
199
|
-
- weight is optional (default 1) and must be a number in (0, ${MAX_WEIGHT}]. It is the slot's active-session budget
|
|
197
|
+
- any number of slots; a model may appear on more than one line, and each line is its own slot with its own budget
|
|
198
|
+
- 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
199
|
- \`#\` starts a comment (whole-line or trailing); blank lines are ignored.
|
|
201
|
-
If the instruction names a model ambiguously,
|
|
200
|
+
If the instruction names a model ambiguously, stop and ask rather than guessing.
|
|
202
201
|
|
|
203
202
|
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
203
|
|
|
@@ -224,6 +223,11 @@ $ARGUMENTS
|
|
|
224
223
|
|
|
225
224
|
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
225
|
},
|
|
226
|
+
"reprocess-timeline": {
|
|
227
|
+
description: "Rebuild this session's timeline from its full transcript",
|
|
228
|
+
agent: "implement",
|
|
229
|
+
template: "Call the `timeline_reprocess` tool exactly once with no arguments. Then reply with only its output."
|
|
230
|
+
},
|
|
227
231
|
"image-in-cb": {
|
|
228
232
|
description: "Attach the clipboard image to this message \u2014 macOS needs pngpaste, Linux needs wl-paste or xclip.",
|
|
229
233
|
template: "$ARGUMENTS"
|