@lawaty/lacode 1.0.3 → 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 +102 -1
- package/README.md +47 -15
- package/dist/agents.js +376 -247
- package/dist/commands.js +38 -18
- package/dist/index.js +2179 -489
- package/dist/lacode-voice.js +2351 -0
- package/dist/lacode.js +23 -2
- package/dist/pool.js +73 -38
- package/dist/tui.js +1282 -0
- package/docs/CONFIGURATION.md +17 -7
- package/docs/CONTEXT.md +80 -10
- package/docs/INSTALL.md +2 -2
- 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/docs/CONFIGURATION.md
CHANGED
|
@@ -23,12 +23,22 @@ allowlist anywhere in the package, and no cost check of any kind: only the
|
|
|
23
23
|
structure is validated. The bundled preset is built from free models because
|
|
24
24
|
that is a sensible default, not because the pool requires it.
|
|
25
25
|
|
|
26
|
+
A slot's `weight` (default 1) is both its concurrency budget and its cap. The
|
|
27
|
+
pool fills slots in declaration order in one pass; there is no slot cap, and a
|
|
28
|
+
model may appear on more than one line — each line is its own slot with its own
|
|
29
|
+
weight. A `tier` key is the deprecated spelling of `weight`.
|
|
30
|
+
|
|
31
|
+
A slot may also declare a shared quota bucket with `b:<name>` on the line (or a
|
|
32
|
+
`bucket` field in JSON). A limit on any member benches the whole bucket, because
|
|
33
|
+
the sibling would spend the same exhausted window; without one, the model is its
|
|
34
|
+
own bucket. `pool_status` shows each shared bucket's effective capacity.
|
|
35
|
+
|
|
26
36
|
|
|
27
37
|
## Which models run which agent
|
|
28
38
|
|
|
29
39
|
Only the **pool** writes a model onto a pooled agent, from whichever source wins
|
|
30
40
|
above. The pooled bases (`explore`, `implement`, `context`)
|
|
31
|
-
and their hidden
|
|
41
|
+
and their hidden `<base>-<slot>` variants therefore carry no model of their own: a
|
|
32
42
|
model written next to an agent would be a second source of truth that silently
|
|
33
43
|
wins whenever routing falls through. Spawn a base and let the pool route it.
|
|
34
44
|
|
|
@@ -39,7 +49,7 @@ wins whenever routing falls through. Spawn a base and let the pool route it.
|
|
|
39
49
|
work around by configuring it.** If you keep a global `"tools"` block in your
|
|
40
50
|
config — for example `"tools": { "playwright_*": false }` — then the pooled
|
|
41
51
|
exploration and implementation bases (`explore` and `implement`, plus their
|
|
42
|
-
hidden
|
|
52
|
+
hidden `<base>-<slot>` pool variants, one per resolved slot) lose browser access, and **nothing tells them why**: the tool call is simply
|
|
43
53
|
refused mid-task. `context` gets no grant either, which is correct — it
|
|
44
54
|
writes the context map and has no business in a browser.
|
|
45
55
|
|
|
@@ -89,11 +99,11 @@ mention/direct-session case cannot be detected before it runs.
|
|
|
89
99
|
|
|
90
100
|
## What gets injected
|
|
91
101
|
|
|
92
|
-
**
|
|
93
|
-
`context`,
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
102
|
+
**One key per base, plus a variant per slot**: the 5 bases `build`, `plan`,
|
|
103
|
+
`explore`, `implement`, `context`, one hidden `<base>-<slot>` pool variant per
|
|
104
|
+
resolved slot for each of the 3 pooled bases, and the hidden `instructions`
|
|
105
|
+
agent — the one writer of the project's root `AGENTS.md`, reachable only from
|
|
106
|
+
the `/agent-instructions` command.
|
|
97
107
|
|
|
98
108
|
`build` and `plan` exist in opencode as built-ins, so on a stock config those
|
|
99
109
|
two keys are already taken and merge-without-clobber keeps the built-in — the
|
package/docs/CONTEXT.md
CHANGED
|
@@ -57,7 +57,7 @@ a work list rather than a map:
|
|
|
57
57
|
| `contexts.md` | The major subsystems. For each: what it is for, where to start reading it, what it depends on, what must not change while you are in there. Read this for a subsystem question. |
|
|
58
58
|
| `conventions.md` | Repository-specific rules: naming, module layout, error handling, testing, persistence. Read this before writing code, not after. |
|
|
59
59
|
| `workflows.md` | The few runtime flows worth tracing end to end — how a request becomes a mutation, how a plugin hook reaches the thing it hooks. Read this when you need to know what actually happens at runtime. |
|
|
60
|
-
| `decisions.md` | Architectural
|
|
60
|
+
| `decisions.md` | Architectural constraints that bind future work and that the repository's own decision log does not already record. Each entry states the constraint and points at the page or path that owns it. Capped at 120 non-blank lines; decision records and deliberation are not copied here. |
|
|
61
61
|
| `active.md` | The active backlog: work in flight, grouped by the ISO week each task was opened in. A task that is not marked `[x]` is open and is never removed; a task that is finished and dated is kept for a week after it was finished, then dropped. The plugin enforces that window — see [How it stays current](#how-it-stays-current). Not a navigation file: there is no question it is the answer to. |
|
|
62
62
|
|
|
63
63
|
The project's root `AGENTS.md` is the standing-instructions home: the client's
|
|
@@ -67,8 +67,51 @@ no prescribed structure. It has exactly one writer — the hidden `instructions`
|
|
|
67
67
|
agent, reachable only from the `/agent-instructions` command.
|
|
68
68
|
|
|
69
69
|
Files the user or the plan explicitly names always win over the map. The map is
|
|
70
|
-
a shortcut to the right starting point, not an authority
|
|
71
|
-
|
|
70
|
+
a shortcut to the right starting point, not an authority. When it disagrees with
|
|
71
|
+
**code**, the code is right and the map is wrong. When it disagrees with a
|
|
72
|
+
**project page**, neither side is automatically right: the map may be current
|
|
73
|
+
and the page stale (that has happened). The disagreement is reported and marked
|
|
74
|
+
`[disputed]`; the page's owner resolves it. The cartographer never silently
|
|
75
|
+
overwrites either side.
|
|
76
|
+
|
|
77
|
+
## The entry contract
|
|
78
|
+
|
|
79
|
+
Every navigation entry has one shape, in every file:
|
|
80
|
+
|
|
81
|
+
```markdown
|
|
82
|
+
- [binding] The operative rule, in at most three non-blank lines. — See `docs/DESIGN.md` §The two halves (verified 2026-10-26)
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
- **A provenance marker.** `[binding]` is a constraint established against the
|
|
86
|
+
source and exempt from age expiry. `[provisional]` is an inference or an
|
|
87
|
+
unverified claim and expires 30 days after `verified`. `[disputed]` records a
|
|
88
|
+
map/page disagreement and is kept, reported, and exempt from age until the
|
|
89
|
+
owner resolves it. There is no second confidence dialect.
|
|
90
|
+
- **The rule, not the deliberation.** No Context / Decision / Consequences /
|
|
91
|
+
Alternatives blocks, status lines, or decision records. A reader needs the
|
|
92
|
+
constraint, not the meeting that produced it.
|
|
93
|
+
- **A pointer.** A fact whose home is a page cites the page **and** its section
|
|
94
|
+
(`docs/DESIGN.md §Retention`). A fact whose home is code cites the path, with
|
|
95
|
+
an optional line or symbol. No pointer means the content has no source in the
|
|
96
|
+
tree — it is reported as unarchived, never emitted as though it were derived.
|
|
97
|
+
- **A verification date.** `(verified YYYY-MM-DD)` drives both the lifecycle and
|
|
98
|
+
the drift check.
|
|
99
|
+
|
|
100
|
+
Every file has a hard cap, counted in **non-blank lines**: architecture 150,
|
|
101
|
+
contexts 200, conventions 150, workflows 150, decisions 120, active 60. Lines
|
|
102
|
+
are the unit because they are what a reader pays for, they bound an entry that
|
|
103
|
+
wraps to three lines, and the validator can count them exactly; an entry count
|
|
104
|
+
does not bound cost, and a token count needs a tokenizer and is model-specific.
|
|
105
|
+
The cap is enforced by a post-generation validator, not only by the prompt.
|
|
106
|
+
|
|
107
|
+
The lifecycle generalises the window `active.md` already had. A finished backlog
|
|
108
|
+
task is dropped after 7 days. A `[provisional]` entry in any navigation file is
|
|
109
|
+
dropped 30 days after its `verified` date unless it is re-verified. `[binding]`
|
|
110
|
+
and `[disputed]` never expire by age. An entry whose cited source no longer
|
|
111
|
+
exists is **never** auto-dropped: it may be the only copy, so it is reported as
|
|
112
|
+
unarchived and kept. Only provisional entries whose source still resolves are
|
|
113
|
+
eligible for the sweep. The arithmetic lives in `src/lib/map-schema.ts`; the
|
|
114
|
+
cartographer writes the shape, code decides what has aged out.
|
|
72
115
|
|
|
73
116
|
The map is **local and untracked**. It describes *your* checkout on *this*
|
|
74
117
|
machine, so committing it would churn on every session that edits a file, and
|
|
@@ -110,6 +153,18 @@ a new or moved entry point, a changed workflow, a new convention, an
|
|
|
110
153
|
architectural decision, an undocumented invariant — and to report
|
|
111
154
|
"no update needed" otherwise.
|
|
112
155
|
|
|
156
|
+
After the turn ends, the plugin applies the per-file lifecycle and then audits
|
|
157
|
+
the map. The audit is **report-only**: it never rewrites a claim and never
|
|
158
|
+
deletes an entry whose source is gone. It checks the schema, each file's line
|
|
159
|
+
budget, dangling paths and `§sections`, a numeric claim absent from its cited
|
|
160
|
+
source, a possible negation conflict, a source modified after its `verified`
|
|
161
|
+
date, tables and fenced blocks (restatements), duplicate rules across files,
|
|
162
|
+
archive-of-record language, and whether each file still answers its charter. A
|
|
163
|
+
failure is a loud `error` in the log; it is what turns the prompt's rules into a
|
|
164
|
+
gate. The deterministic half is `src/lib/map-schema.ts`; the semantic drift pass
|
|
165
|
+
is the cartographer's, and it reports a page disagreement instead of picking a
|
|
166
|
+
side.
|
|
167
|
+
|
|
113
168
|
If the map is missing entirely, nothing breaks: agents run the check, find no
|
|
114
169
|
`.opencode/context/`, and explore from scratch. What tells them to run the check
|
|
115
170
|
is an entry appended to your config's `instructions` array: your own entries are
|
|
@@ -138,17 +193,25 @@ your source.
|
|
|
138
193
|
If you have defined a command of the same name, yours wins and is left exactly
|
|
139
194
|
as you wrote it — the same merge-without-clobber rule the agents follow.
|
|
140
195
|
|
|
141
|
-
`active.md` is the
|
|
142
|
-
|
|
196
|
+
`active.md` is the file the plugin maintains as a backlog rather than as a
|
|
197
|
+
navigation map. The cartographer records what happened — work in flight, and a
|
|
143
198
|
task marked finished with the date it finished — and the plugin decides what is
|
|
144
199
|
still worth keeping: the file is regrouped by week and a task that was finished
|
|
145
200
|
more than a week ago is dropped. The arithmetic lives in
|
|
146
201
|
`src/lib/active-backlog.ts`, not in the cartographer's prompt, because a rule a
|
|
147
202
|
model applies is a rule the model applies when it feels like it, and the failure
|
|
148
|
-
is silent — the backlog just grows. The plugin normalizes the file on every
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
203
|
+
is silent — the backlog just grows. The plugin normalizes the file on every edit
|
|
204
|
+
of it and once when the session starts, so a hand edit is grouped and pruned
|
|
205
|
+
whether the cartographer ran or not, and the file is rewritten only when its
|
|
206
|
+
canonical form differs from what is on disk, so the write cannot loop.
|
|
207
|
+
|
|
208
|
+
The same code owns the navigation files' lifecycle, but it may only **drop** an
|
|
209
|
+
entry that is `[provisional]`, past its 30-day window, and whose cited source
|
|
210
|
+
still exists. It never drops a binding constraint or disputed entry, and it
|
|
211
|
+
never drops an entry with no source — that is the one case where the map may be
|
|
212
|
+
the only copy. The plugin is not a second writer of the map in the permission
|
|
213
|
+
sense: `context` is still the only **agent** that writes it. The lifecycle is
|
|
214
|
+
housekeeping by code, the same category as the backlog window.
|
|
152
215
|
|
|
153
216
|
## One writer
|
|
154
217
|
|
|
@@ -226,14 +289,21 @@ to check is that permission rule, not the map's content.
|
|
|
226
289
|
| `auto update finished` | The run completed and changed at least one map file. |
|
|
227
290
|
| `auto update finished without changing the map` | Nothing landed. Either nothing needed saying, or the cartographer could not write. |
|
|
228
291
|
| `auto update failed: …` | The run errored. The paths it was given are queued again for the next window, so the work is not lost. |
|
|
292
|
+
| `map lifecycle: dropped N expired provisional entr… from <file>` | The code sweep dropped an entry past its 30-day window. Binding and disputed entries are never dropped. |
|
|
293
|
+
| `context map validation passed` | Every file is in schema and within budget, every pointer resolves, and the drift checks found nothing. |
|
|
294
|
+
| `context map validation failed: N issue(s)` | The run's map is off schema or over budget, cites a missing path/section, restates a page, or has a possible contradiction. The map is **not** rewritten; the log names up to 20 issues and the paths to fix. |
|
|
229
295
|
| `context map is not writable: …` | Your config's one-writer rule does not match the path opencode evaluates. Nothing will update until you fix it. |
|
|
230
296
|
| `context map has more than one writer: …` | An agent other than `context` would be granted the map. The offending agent and the winning rule are named. |
|
|
231
297
|
|
|
232
298
|
## Reference
|
|
233
299
|
|
|
234
300
|
- The agent and command definitions, as injected: `src/agents.ts`, `src/commands.ts`.
|
|
235
|
-
- The
|
|
301
|
+
- The prompt the model actually runs, in one place: `src/lib/cartographer-prompt.ts`; the tracked copy is `.opencode/prompts/context.txt`, and a test pins equality.
|
|
302
|
+
- The idle/edit trigger and the post-run audit wiring: `src/plugins/context-autoupdate.ts`.
|
|
303
|
+
- The schema, budget, lifecycle and deterministic drift check: `src/lib/map-schema.ts`.
|
|
304
|
+
- The active backlog's window: `src/lib/active-backlog.ts`.
|
|
236
305
|
- The one-writer rule check: `src/lib/writer-rule.ts`.
|
|
306
|
+
- Fixtures and their report: `tests/fixtures/context-map/`, `scripts/context-map-fixture.mjs`.
|
|
237
307
|
- The behavioural contracts, with acceptance criteria a test asserts:
|
|
238
308
|
[USER-STORIES.md](USER-STORIES.md), US-19 to US-22.
|
|
239
309
|
- Tests: `tests/stories/g-context-management.test.mjs`.
|
package/docs/INSTALL.md
CHANGED
|
@@ -181,8 +181,8 @@ or no GNU `timeout` — rather than passing silently or failing confusingly.
|
|
|
181
181
|
|
|
182
182
|
## The `lacode` CLI
|
|
183
183
|
|
|
184
|
-
The package also ships the `lacode` CLI (`notify`, `forward`, `doctor`, `
|
|
185
|
-
`update`, `receive`, `tunnel setup`) — see
|
|
184
|
+
The package also ships the `lacode` CLI (`notify`, `forward`, `doctor`, `feedback`,
|
|
185
|
+
`version`, `update`, `receive`, `tunnel setup`) — see
|
|
186
186
|
[Remote hosts](../README.md#remote-hosts). The plugin install does **not** put it on your
|
|
187
187
|
`PATH`; run it from the package's `node_modules/.bin`, or install just the CLI
|
|
188
188
|
globally with `npm i -g @lawaty/lacode` if you want the bare command. (That
|
package/docs/USER-STORIES.md
CHANGED
|
@@ -15,7 +15,7 @@ line. Criteria that cannot be observed without a running server are not in this
|
|
|
15
15
|
document; everything here is testable offline with a stub client and a
|
|
16
16
|
`mkdtempSync` state directory.
|
|
17
17
|
|
|
18
|
-
**Terminology.** A *slot* is one entry in `presets/free-tier.json`, carrying a model and a `weight`. A slot's `weight` is its active-session budget for one pass: the number of concurrent claims it may hold before the next slot is preferred. A *pass* is one declaration-order sweep of the slot list; the pool runs
|
|
18
|
+
**Terminology.** A *slot* is one entry in `presets/free-tier.json`, carrying a model and a `weight`. A slot's `weight` is its active-session budget for one pass: the number of concurrent claims it may hold before the next slot is preferred. A *pass* is one declaration-order sweep of the slot list; the pool runs one pass, and a slot's cap is its weight. There is no slot cap, and a model may appear on more than one line — each line is its own slot with its own weight. Preference is by declaration order, never by weight. A *base* is a pooled agent type (`explore`), and its slot holders are `<base>-<slot>`, hidden in `opencode.jsonc`. A *claim* is a running spawn recorded in `claims.<pid>.json`. A *context map* is the five-file `.opencode/context/` codemap described in Epic G.
|
|
19
19
|
|
|
20
20
|
---
|
|
21
21
|
|
|
@@ -92,18 +92,18 @@ The reconciliation with US-7 is that **acceptance and endorsement are different
|
|
|
92
92
|
- [ ] Given every model is cooling, When a spawn is routed, Then the least-loaded slot is chosen anyway (degrade, never block).
|
|
93
93
|
- [ ] Given a limit error names a reset time, When the cooldown is recorded, Then the stated reset is used instead of a guessed backoff.
|
|
94
94
|
|
|
95
|
-
### US-5: Slots fill in declaration order
|
|
95
|
+
### US-5: Slots fill in declaration order in one pass, with repeats
|
|
96
96
|
**As a** user, **I want** work to prefer the first slot and spill to later slots only when the earlier ones are full, **so that** a healthy preferred model is used before fallbacks and no slot is used while an earlier usable slot has room.
|
|
97
97
|
|
|
98
98
|
**Story ID(s) for tests:** US-5
|
|
99
99
|
**Acceptance criteria:**
|
|
100
|
-
- [ ] Given live claims, When the pool has room
|
|
101
|
-
- [ ] Given
|
|
102
|
-
- [ ] Given a slot is cooling or unavailable, When its position
|
|
100
|
+
- [ ] Given live claims, When the pool has room, Then the lowest-index usable slot whose load is below its weight is chosen, and a slot declared later is not used until every earlier usable slot has reached its weight.
|
|
101
|
+
- [ ] Given a model appears on more than one line, When the pool loads, Then each line is its own slot with its own weight, and the later line adds seats at its own position.
|
|
102
|
+
- [ ] Given a slot is cooling or unavailable, When its position comes up, Then it is skipped and the pass continues to the next slot.
|
|
103
103
|
- [ ] Given a claim is released, When the next spawn is routed, Then the slot that freed capacity is preferred again in declaration order; no stored position keeps it waiting.
|
|
104
|
-
- [ ] Given every usable slot is at its
|
|
105
|
-
- [ ] Given all slots carry equal weight, When spawns are routed, Then the same ordered
|
|
106
|
-
- [ ] Given per-slot load is read, When it is asserted, Then a slot's load never exceeds
|
|
104
|
+
- [ ] Given every usable slot is at its weight, When another spawn is routed, Then it goes to the least-loaded usable slot as overflow, and the pool never refuses work.
|
|
105
|
+
- [ ] Given all slots carry equal weight, When spawns are routed, Then the same ordered one-pass fill applies.
|
|
106
|
+
- [ ] Given per-slot load is read, When it is asserted, Then a slot's load never exceeds its weight except through overflow.
|
|
107
107
|
|
|
108
108
|
### US-6: Live pool visibility
|
|
109
109
|
**As a** user, **I want** to see per-slot load and which model is cooling and why, **so that** I understand where my subagents are going.
|
|
@@ -113,8 +113,8 @@ The reconciliation with US-7 is that **acceptance and endorsement are different
|
|
|
113
113
|
- [ ] Given claims are held by several processes, When the status view is requested, Then it shows every process's claims and the aggregate load per slot.
|
|
114
114
|
- [ ] Given a model is cooling with a stated reason, When the status view is requested, Then the model is listed as cooling together with that reason.
|
|
115
115
|
- [ ] Given a claim file is older than the dead-file threshold, When the status view is computed, Then the stale claim is pruned rather than counted.
|
|
116
|
-
- [ ] Given a slot is at its
|
|
117
|
-
- [ ] Given the pool is filling, When the status view is requested, Then it shows whether the next spawn
|
|
116
|
+
- [ ] Given a slot is at its weight, When the status view is requested, Then it is not marked as the slot the next spawn would take while any usable slot is below its weight; when every usable slot is at its weight, the least-loaded slot is marked next.
|
|
117
|
+
- [ ] Given the pool is filling, When the status view is requested, Then it shows whether the next spawn is a normal pick or overflow.
|
|
118
118
|
|
|
119
119
|
### US-7: Non-free models still work
|
|
120
120
|
**As a** user who wants a paid model in the pool, **I want** to supply any model list, **so that** the pool is not restricted to free tiers.
|
|
@@ -123,7 +123,7 @@ The reconciliation with US-7 is that **acceptance and endorsement are different
|
|
|
123
123
|
**Acceptance criteria:**
|
|
124
124
|
- [ ] Given a slot names a paid model, When the pool loads, Then the slot is accepted and routed to without a free-tier filter rejecting it.
|
|
125
125
|
- [ ] Given a model id contains a vendor prefix with slashes (e.g. `openrouter/nvidia/model:free`), When it is split into provider and model id, Then the split is on the first slash only and the id tail is preserved.
|
|
126
|
-
- [ ] Given more slots
|
|
126
|
+
- [ ] Given a pool file declares more slots than the built-in warning threshold, When the pool loads, Then every entry is kept and a warning names the count; there is no slot cap.
|
|
127
127
|
- [ ] Given a provider configured with **no** `whitelist` key, When the config hook binds a slot on that provider, Then no `whitelist` is written for it, because an absent whitelist already allows every model the provider offers.
|
|
128
128
|
- [ ] Given a provider the pool never names a slot on, When the config hook runs, Then that provider entry is left exactly as it was, with no `whitelist` key added.
|
|
129
129
|
- [ ] Given a provider that already carries a `whitelist`, When the config hook binds a slot model on it, Then the model id is appended and every entry the user wrote is preserved in order.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lawaty/lacode",
|
|
3
|
-
"version": "1.0
|
|
3
|
+
"version": "1.2.0",
|
|
4
4
|
"description": "Ultimate opencode setup for Go users — cost-controlled pooled subagents, hang recovery, and notifications for opencode.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -34,11 +34,13 @@
|
|
|
34
34
|
"./pool": "./dist/pool.js",
|
|
35
35
|
"./agents": "./dist/agents.js",
|
|
36
36
|
"./commands": "./dist/commands.js",
|
|
37
|
+
"./tui": "./dist/tui.js",
|
|
37
38
|
"./tunnel": "./dist/tunnel.js",
|
|
38
39
|
"./package.json": "./package.json"
|
|
39
40
|
},
|
|
40
41
|
"bin": {
|
|
41
|
-
"lacode": "
|
|
42
|
+
"lacode": "dist/lacode.js",
|
|
43
|
+
"lacode-voice": "dist/lacode-voice.js"
|
|
42
44
|
},
|
|
43
45
|
"files": [
|
|
44
46
|
"dist",
|
|
@@ -47,14 +49,16 @@
|
|
|
47
49
|
"LICENSE",
|
|
48
50
|
"CHANGELOG.md",
|
|
49
51
|
"README.md",
|
|
50
|
-
"docs"
|
|
52
|
+
"docs",
|
|
53
|
+
"scripts/score-models.mjs"
|
|
51
54
|
],
|
|
52
55
|
"scripts": {
|
|
53
56
|
"build": "node scripts/build.mjs",
|
|
54
57
|
"prepack": "npm run build",
|
|
55
58
|
"postpublish": "node scripts/sync-hosts.mjs",
|
|
56
59
|
"test": "node --test tests/",
|
|
57
|
-
"benchmark": "node tests/benchmark/run.mjs"
|
|
60
|
+
"benchmark": "node tests/benchmark/run.mjs",
|
|
61
|
+
"score": "node scripts/score-models.mjs"
|
|
58
62
|
},
|
|
59
63
|
"devDependencies": {
|
|
60
64
|
"esbuild": "^0.28.2"
|
|
@@ -62,6 +66,10 @@
|
|
|
62
66
|
"peerDependencies": {
|
|
63
67
|
"@opencode-ai/plugin": "1.18.34"
|
|
64
68
|
},
|
|
69
|
+
"optionalDependencies": {
|
|
70
|
+
"@picovoice/porcupine-node": "^4.0.2",
|
|
71
|
+
"vosk": "^0.3.39"
|
|
72
|
+
},
|
|
65
73
|
"publishConfig": {
|
|
66
74
|
"tag": "latest"
|
|
67
75
|
}
|
package/presets/README.md
CHANGED
|
@@ -45,9 +45,9 @@ opencode/glm-5.3-flash-free 1
|
|
|
45
45
|
|
|
46
46
|
`#` starts a comment (whole-line or trailing), blank lines are ignored, and the
|
|
47
47
|
weight is optional (default 1). The rules are exactly the same as the JSON form
|
|
48
|
-
below:
|
|
49
|
-
accepts either shape in the same file, so a leading
|
|
50
|
-
else means the flat form.
|
|
48
|
+
below: any number of slots, a model may repeat on another line, split on the
|
|
49
|
+
first slash only. The loader accepts either shape in the same file, so a leading
|
|
50
|
+
`{` means JSON and anything else means the flat form.
|
|
51
51
|
|
|
52
52
|
## Schema
|
|
53
53
|
|
|
@@ -63,7 +63,7 @@ else means the flat form.
|
|
|
63
63
|
| Field | Required | Meaning |
|
|
64
64
|
|-------|----------|---------|
|
|
65
65
|
| `model` | yes | `provider/model-id`, split on the **first** slash only |
|
|
66
|
-
| `weight` | no | active-session budget
|
|
66
|
+
| `weight` | no | active-session budget; the pool's capacity is the sum of the weights. Absent = 1 |
|
|
67
67
|
|
|
68
68
|
`$comment` is a JSON-legal array of strings and is ignored by the loader; the
|
|
69
69
|
bundled preset uses it to explain itself in place.
|
|
@@ -74,13 +74,13 @@ The loader checks shape and nothing else:
|
|
|
74
74
|
|
|
75
75
|
- `slots` must be a non-empty array
|
|
76
76
|
- each `model` must contain a `/`
|
|
77
|
-
-
|
|
77
|
+
- a model may appear on more than one line; each line is its own slot with its
|
|
78
|
+
own load counter and weight
|
|
78
79
|
- `weight` must be a number in `(0, 100]`; a bad one **warns and uses 1** rather
|
|
79
80
|
than dropping the slot, because losing a model to a typo in a count costs real
|
|
80
81
|
capacity
|
|
81
|
-
-
|
|
82
|
-
|
|
83
|
-
would route at an agent that does not exist
|
|
82
|
+
- there is no slot cap: one hidden variant agent is declared per base per
|
|
83
|
+
resolved slot, and the watcher declares a variant if a reload grows the table
|
|
84
84
|
- `tier` is accepted as an alias for `weight` (`primary` → 2,
|
|
85
85
|
`overflow` → 1) and warns
|
|
86
86
|
|
|
@@ -92,20 +92,18 @@ because the pool requires it.
|
|
|
92
92
|
|
|
93
93
|
## Weight, precisely
|
|
94
94
|
|
|
95
|
-
A weight is a slot's active-session budget
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
95
|
+
A weight is a slot's active-session budget: the number of concurrent claims it
|
|
96
|
+
may hold before the next slot is preferred. The pool's capacity is the **sum of
|
|
97
|
+
the weights**, so a weight-3 slot may hold 3 concurrent sessions and listing a
|
|
98
|
+
model on two lines gives it two independent budgets.
|
|
99
99
|
|
|
100
|
-
Routing
|
|
100
|
+
Routing is one pass:
|
|
101
101
|
|
|
102
|
-
1.
|
|
103
|
-
2. once every usable slot has reached its weight, pass 2 repeats the order,
|
|
104
|
-
filling each usable slot up to twice its weight
|
|
102
|
+
1. fill each usable slot up to its weight, in declaration order
|
|
105
103
|
|
|
106
|
-
A slot is skipped when it is cooling
|
|
107
|
-
|
|
108
|
-
|
|
104
|
+
A slot is skipped when it is cooling or unavailable. When every usable slot is
|
|
105
|
+
at its weight, the pool is at capacity and overflow goes to the least loaded
|
|
106
|
+
usable slot; the pool never refuses work.
|
|
109
107
|
|
|
110
108
|
Live claims decide the next slot: a released claim immediately reopens its
|
|
111
109
|
slot, and there is no stored position.
|
package/presets/free-tier.json
CHANGED
|
@@ -9,25 +9,23 @@
|
|
|
9
9
|
"Slot N is the same model for every base agent, which is what makes load shared.",
|
|
10
10
|
"",
|
|
11
11
|
"RULES, all validated at startup: models are written provider/model-id, split on the FIRST",
|
|
12
|
-
"slash only so openrouter's vendor-prefixed ids (nvidia/x:free) resolve
|
|
13
|
-
"
|
|
14
|
-
"
|
|
12
|
+
"slash only so openrouter's vendor-prefixed ids (nvidia/x:free) resolve. A model may",
|
|
13
|
+
"appear on more than one line: each line is its own slot with its own budget.",
|
|
14
|
+
"ANY NUMBER OF SLOTS: one hidden variant per base per slot is declared from the resolved",
|
|
15
|
+
"table, and the watcher declares a variant when a reload grows the table. There is no slot cap.",
|
|
15
16
|
"",
|
|
16
17
|
"WEIGHT IS THE ACTIVE-SESSION BUDGET FOR ONE PASS: a slot may hold up to its weight in",
|
|
17
18
|
"concurrent claims before the next slot is preferred. Slots are filled in declaration",
|
|
18
19
|
"order; preference is order, never weight.",
|
|
19
|
-
"Routing
|
|
20
|
-
"
|
|
21
|
-
"
|
|
22
|
-
"
|
|
23
|
-
"A slot is skipped when it is cooling, unavailable, or at its absolute cap (twice its",
|
|
24
|
-
"weight). When every usable slot is at its absolute cap, overflow goes to the least",
|
|
25
|
-
"loaded usable slot; the pool never refuses work.",
|
|
20
|
+
"Routing is ONE ordered pass: each usable slot fills up to its weight, in declaration",
|
|
21
|
+
"order. A slot is skipped when it is cooling or unavailable. When every usable slot is at",
|
|
22
|
+
"its weight, the pool is at capacity (the sum of the weights) and overflow goes to the",
|
|
23
|
+
"least-loaded usable slot; the pool never refuses work.",
|
|
26
24
|
"Live claims decide the next slot: a released claim immediately reopens its slot, and",
|
|
27
25
|
"there is no stored position.",
|
|
28
26
|
"",
|
|
29
|
-
"With this file: weights 3,2,1,1 give 7 active sessions in
|
|
30
|
-
"
|
|
27
|
+
"With this file: weights 3,2,1,1 give 7 active sessions in total. To give a model more",
|
|
28
|
+
"capacity, list it again on another line with its own weight.",
|
|
31
29
|
"Equal weights give a plain pass in list order.",
|
|
32
30
|
"",
|
|
33
31
|
"DECLARATION ORDER IS PREFERENCE. Slots are filled in declaration order, so the slot",
|
|
@@ -44,7 +42,7 @@
|
|
|
44
42
|
"List order sets preference: earlier slots are preferred first, so put the slots you",
|
|
45
43
|
"want worked first at the top of the list.",
|
|
46
44
|
"",
|
|
47
|
-
"COST IS THE DEFAULT, NOT A CONSTRAINT. These
|
|
45
|
+
"COST IS THE DEFAULT, NOT A CONSTRAINT. These slots are free because that is what",
|
|
48
46
|
"the pool is FOR: mechanical work -- reading files, grepping, mechanical edits, cartography.",
|
|
49
47
|
"They are not here to be the smartest model in the system. The point of putting that work",
|
|
50
48
|
"on a near-free model is that the mechanical tokens stop being spent by your expensive",
|