@lawaty/lacode 0.5.0 → 0.6.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
@@ -9,8 +9,29 @@ commit as the changelog entry it describes.
9
9
 
10
10
  ## [Unreleased]
11
11
 
12
+ ## [0.6.0] — a client-only /pool command, and README corrections
13
+
14
+ ### Added
15
+
16
+ - **`/pool <instruction>`** — a client-only command that rewrites this machine's
17
+ pool override, `~/.config/lacode/pool.txt`, from a short instruction. It
18
+ applies the loader's rules (at most 4 distinct slots, weight in (0, 100], line
19
+ order is the rotation), writes the WHOLE file rather than a patch, reports the
20
+ before → after table, and reminds you that nothing hot-reloads.
21
+
22
+ ### Fixed
23
+
24
+ - README: the count of agent keys deliberately *not* injected was stated as
25
+ three, not the five `docs/CONFIGURATION.md` lists; and the `lacode` CLI section
26
+ was missing `version`, `update`, `receive` and `tunnel setup`.
27
+ `docs/INSTALL.md`'s CLI list gained `version` and `update`. A stale test title
28
+ ("three commands") was corrected to match what it asserts.
29
+
12
30
  ## [0.5.0] — a user-only /manager-directive, instant limit failover, and the longcat preset
13
31
 
32
+ Released on the npm **`beta`** dist-tag (not `latest`) while the project is
33
+ experimental; `latest` remains 0.4.2.
34
+
14
35
  ### Added
15
36
 
16
37
  - **`/manager-directive <text>`** — a client-only command that runs a hidden
@@ -193,7 +214,8 @@ commit as the changelog entry it describes.
193
214
 
194
215
  - First published release: pooled subagents over a shared set of models.
195
216
 
196
- [Unreleased]: https://github.com/lawaty/lacode/compare/v0.5.0...HEAD
217
+ [Unreleased]: https://github.com/lawaty/lacode/compare/v0.6.0...HEAD
218
+ [0.6.0]: https://github.com/lawaty/lacode/compare/v0.5.0...v0.6.0
197
219
  [0.5.0]: https://github.com/lawaty/lacode/compare/v0.4.2...v0.5.0
198
220
  [0.4.1]: https://github.com/lawaty/lacode/compare/v0.4.0...v0.4.1
199
221
  [0.4.0]: https://github.com/lawaty/lacode/compare/v0.3.0...v0.4.0
package/README.md CHANGED
@@ -2,6 +2,11 @@
2
2
 
3
3
  > Ultimate opencode setup for Go users
4
4
 
5
+ > **Beta — experimental, changing fast.** For the next month LaCode ships on the
6
+ > npm **`beta`** dist-tag rather than `latest`, so nothing pulls it by accident:
7
+ > install `@lawaty/lacode@beta`, or pin an exact version. Interfaces here can
8
+ > change between releases without a major bump while the project stabilises.
9
+
5
10
  LaCode is an opencode plugin. It routes delegated work onto a weighted pool of
6
11
  cheap models instead of the expensive one, reaps subagents that hang without
7
12
  telling anyone, keeps a maintained map of your repository so agents stop
@@ -178,12 +183,12 @@ only thing that works: naming a variant lets the caller choose the provider
178
183
  directly, which is exactly what the rotation and the per-slot ceiling exist to
179
184
  prevent, so it is refused.
180
185
 
181
- Five commands: `/context-init`, `/context-update`, `/context-review`,
182
- `/lacode-doctor` and `/manager-directive`. Everything is added **only when your
183
- config does not already
186
+ Six commands: `/context-init`, `/context-update`, `/context-review`,
187
+ `/lacode-doctor`, `/manager-directive` and `/pool`. Everything is added **only
188
+ when your config does not already
184
189
  define it** — an agent or command you wrote yourself is left byte-identical,
185
190
  never patched or partially merged. `{"enabled": false}` turns the whole thing off
186
- without leaving a trace. The exact injection list, and the three keys that are
191
+ without leaving a trace. The exact injection list, and the five keys that are
187
192
  deliberately *not* injected, are in
188
193
  [docs/CONFIGURATION.md](docs/CONFIGURATION.md#what-gets-injected).
189
194
 
@@ -214,6 +219,7 @@ are reading:
214
219
  /context-review # audit it for staleness, contradictions, duplication and bloat
215
220
  /lacode-doctor # check the pool, model resolution and the OmniRoute gateway. Report only
216
221
  /manager-directive # record a managerial direction in manager.md — client-only, see below
222
+ /pool # rewrite the pool slot table in ~/.config/lacode/pool.txt; restart to apply
217
223
  ```
218
224
 
219
225
  The map is local and untracked — it describes your checkout on your machine, so
@@ -333,6 +339,13 @@ must never be able to fail the process that tried to notify you. `lacode forward
333
339
  HOST -- COMMAND` runs a command on another host over ssh and exits with its
334
340
  status.
335
341
 
342
+ The full CLI is `notify`, `forward`, `doctor`, `version`, `update`, `receive`
343
+ and `tunnel setup`. `version` reports which copy of LaCode is running — and
344
+ whether opencode is loading that same copy — and `update [--check]` refreshes an
345
+ installed one. `receive` is the desktop end of the tunnel (the forced command a
346
+ remote key is restricted to) and `tunnel setup` provisions the key and the
347
+ forward.
348
+
336
349
  Configuration, the payload format, the receiver contract and holding the forward
337
350
  open are in [docs/TUNNEL.md](docs/TUNNEL.md).
338
351
 
package/dist/commands.js CHANGED
@@ -1,6 +1,55 @@
1
1
  // src/lib/manager-directive.ts
2
2
  var MANAGER_AGENT_NAME = "manager";
3
3
 
4
+ // src/lib/pool.ts
5
+ import { existsSync, mkdirSync, readdirSync, readFileSync as readFileSync2, renameSync, statSync, unlinkSync, writeFileSync } from "node:fs";
6
+ import { homedir } from "node:os";
7
+ import { join as join2 } from "node:path";
8
+
9
+ // src/lib/package-root.ts
10
+ import { readFileSync } from "node:fs";
11
+ import { dirname, join } from "node:path";
12
+ import { fileURLToPath } from "node:url";
13
+ var PACKAGE_NAME = "@lawaty/lacode";
14
+ var findPackageRoot = (start) => {
15
+ let dir = start;
16
+ for (let up = 0; up < 6; up++) {
17
+ try {
18
+ if (JSON.parse(readFileSync(join(dir, "package.json"), "utf8")).name === PACKAGE_NAME) return dir;
19
+ } catch {
20
+ }
21
+ const parent = dirname(dir);
22
+ if (parent === dir) break;
23
+ dir = parent;
24
+ }
25
+ return join(start, "..", "..");
26
+ };
27
+ var PACKAGE_ROOT = findPackageRoot(dirname(fileURLToPath(import.meta.url)));
28
+
29
+ // src/lib/pool.ts
30
+ var CLAIM_TTL_MS = 10 * 60 * 1e3;
31
+ var DEAD_FILE_MS = 2 * CLAIM_TTL_MS;
32
+ var ORPHAN_TMP_MS = 60 * 1e3;
33
+ var CATALOG_TTL_MS = 10 * 60 * 1e3;
34
+ var STUCK_MIN_AGE_MS = 5 * 60 * 1e3;
35
+ var STUCK_IDLE_MS = 3 * 60 * 1e3;
36
+ var MAX_CLAIM_AGE_MS = 30 * 60 * 1e3;
37
+ var HANG_COOLDOWN_MS = 10 * 60 * 1e3;
38
+ var UNAVAILABLE_COOLDOWN_MS = 60 * 1e3;
39
+ var MAX_WEIGHT = 100;
40
+ var MAX_SLOTS = 4;
41
+ var COOLDOWN_BASE_MS = 60 * 1e3;
42
+ var COOLDOWN_MAX_MS = 15 * 60 * 1e3;
43
+ var QUOTA_COOLDOWN_BASE_MS = 15 * 60 * 1e3;
44
+ var QUOTA_COOLDOWN_MAX_MS = 6 * 60 * 60 * 1e3;
45
+ var STRIKE_DECAY_MS = 30 * 60 * 1e3;
46
+ var PRESETS_DIR = join2(PACKAGE_ROOT, "presets");
47
+ var DEFAULT_PRESET = join2(PRESETS_DIR, "free-tier.json");
48
+ var USER_CONFIG_DIR = join2(homedir(), ".config", "lacode");
49
+ var USER_MODELS_PATH = join2(USER_CONFIG_DIR, "pool.json");
50
+ var USER_MODELS_TXT = join2(USER_CONFIG_DIR, "pool.txt");
51
+ var CHILD_MEMORY_MS = 30 * 6e4;
52
+
4
53
  // src/commands.ts
5
54
  var COMMANDS = {
6
55
  "context-init": {
@@ -121,6 +170,36 @@ Print one PASS/FAIL line per check below, with the exact fix for any FAIL.
121
170
  whether or not the gateway advertises it, so it needs no allowlist entry.
122
171
 
123
172
  Change nothing. Print a final one-line summary of failures.`
173
+ },
174
+ "pool": {
175
+ description: "Modify this machine's LaCode pool from a short instruction \u2014 the shared free-model slot table in ~/.config/lacode/pool.txt. Restarts opencode afterwards.",
176
+ template: `Apply this change to the LaCode pool on this machine:
177
+
178
+ $ARGUMENTS
179
+
180
+ The pool is ONE global slot table shared by every pooled base (explore, implement, context) \u2014 there are no per-base model lists, so "put X on explore" means "give X a slot", not a per-base override.
181
+
182
+ 1. Establish the baseline. Read \`${USER_MODELS_TXT}\`. If it is absent, the pool is resolving from the bundled preset \`presets/free-tier.json\`; read that file to learn what is in effect. Call the \`pool_status\` tool only if you need live load or cooldown state.
183
+
184
+ 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:
185
+ - one slot per line: \`<provider>/<model-id> [weight]\`
186
+ - at most ${MAX_SLOTS} slots, and a model may appear only once
187
+ - weight is optional (default 1) and must be a number in (0, ${MAX_WEIGHT}]. It is the soft ceiling on concurrent claims and the slot's share of the rotation \u2014 NOT a priority; pick order follows line order.
188
+ - \`#\` starts a comment (whole-line or trailing); blank lines are ignored.
189
+ If the instruction names a model ambiguously, or would need more than ${MAX_SLOTS} slots, stop and ask rather than guessing.
190
+
191
+ 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 the rotation. Keep the existing comment header. If the write fails on a missing directory, create \`~/.config/lacode\` first and retry.
192
+
193
+ 4. Report the before \u2192 after slot table, the resolved path, and this reminder in substance: nothing hot-reloads, so every running opencode process must be restarted before the new pool takes effect.
194
+
195
+ Two traps that fail silently, so check and state them explicitly:
196
+ - A slot's provider must already exist in the user's opencode config, or that slot cannot resolve at all. If the instruction adds a model on a provider that is not configured, say so.
197
+ - If a slot model is \`omniroute/<id>\`, that id must also appear in the plugin's \`omniroute.models\` allowlist (or be the \`combo\` id, which needs no entry) \u2014 otherwise the pool resolves and every spawn fails with \`ProviderModelNotFoundError\`.
198
+ - A provider \`whitelist\` is an allowlist, and the plugin appends your model to it at config time. Nothing for you to do there.
199
+
200
+ If the instruction is to RESET or REVERT rather than to change models, delete \`${USER_MODELS_TXT}\` instead, and report that the pool falls back to the bundled preset \u2014 with the same restart reminder.
201
+
202
+ Change nothing else. In particular do not touch \`presets/free-tier.json\`, \`~/.config/lacode/pool.json\`, \`FALLBACK_SLOTS\` in \`src/lib/pool.ts\`, or \`tests/models-snapshot.json\`: this command owns the per-machine override and nothing else.`
124
203
  }
125
204
  };
126
205
  function applyCommands(cfg, commands = COMMANDS) {
package/dist/index.js CHANGED
@@ -1506,6 +1506,36 @@ Print one PASS/FAIL line per check below, with the exact fix for any FAIL.
1506
1506
  whether or not the gateway advertises it, so it needs no allowlist entry.
1507
1507
 
1508
1508
  Change nothing. Print a final one-line summary of failures.`
1509
+ },
1510
+ "pool": {
1511
+ description: "Modify this machine's LaCode pool from a short instruction \u2014 the shared free-model slot table in ~/.config/lacode/pool.txt. Restarts opencode afterwards.",
1512
+ template: `Apply this change to the LaCode pool on this machine:
1513
+
1514
+ $ARGUMENTS
1515
+
1516
+ The pool is ONE global slot table shared by every pooled base (explore, implement, context) \u2014 there are no per-base model lists, so "put X on explore" means "give X a slot", not a per-base override.
1517
+
1518
+ 1. Establish the baseline. Read \`${USER_MODELS_TXT}\`. If it is absent, the pool is resolving from the bundled preset \`presets/free-tier.json\`; read that file to learn what is in effect. Call the \`pool_status\` tool only if you need live load or cooldown state.
1519
+
1520
+ 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:
1521
+ - one slot per line: \`<provider>/<model-id> [weight]\`
1522
+ - at most ${MAX_SLOTS} slots, and a model may appear only once
1523
+ - weight is optional (default 1) and must be a number in (0, ${MAX_WEIGHT}]. It is the soft ceiling on concurrent claims and the slot's share of the rotation \u2014 NOT a priority; pick order follows line order.
1524
+ - \`#\` starts a comment (whole-line or trailing); blank lines are ignored.
1525
+ If the instruction names a model ambiguously, or would need more than ${MAX_SLOTS} slots, stop and ask rather than guessing.
1526
+
1527
+ 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 the rotation. Keep the existing comment header. If the write fails on a missing directory, create \`~/.config/lacode\` first and retry.
1528
+
1529
+ 4. Report the before \u2192 after slot table, the resolved path, and this reminder in substance: nothing hot-reloads, so every running opencode process must be restarted before the new pool takes effect.
1530
+
1531
+ Two traps that fail silently, so check and state them explicitly:
1532
+ - A slot's provider must already exist in the user's opencode config, or that slot cannot resolve at all. If the instruction adds a model on a provider that is not configured, say so.
1533
+ - If a slot model is \`omniroute/<id>\`, that id must also appear in the plugin's \`omniroute.models\` allowlist (or be the \`combo\` id, which needs no entry) \u2014 otherwise the pool resolves and every spawn fails with \`ProviderModelNotFoundError\`.
1534
+ - A provider \`whitelist\` is an allowlist, and the plugin appends your model to it at config time. Nothing for you to do there.
1535
+
1536
+ If the instruction is to RESET or REVERT rather than to change models, delete \`${USER_MODELS_TXT}\` instead, and report that the pool falls back to the bundled preset \u2014 with the same restart reminder.
1537
+
1538
+ Change nothing else. In particular do not touch \`presets/free-tier.json\`, \`~/.config/lacode/pool.json\`, \`FALLBACK_SLOTS\` in \`src/lib/pool.ts\`, or \`tests/models-snapshot.json\`: this command owns the per-machine override and nothing else.`
1509
1539
  }
1510
1540
  };
1511
1541
  function applyCommands(cfg, commands = COMMANDS) {
package/docs/INSTALL.md CHANGED
@@ -182,7 +182,8 @@ or no GNU `timeout` — rather than passing silently or failing confusingly.
182
182
 
183
183
  ## The `lacode` CLI
184
184
 
185
- The package also ships the `lacode` CLI (`notify`, `forward`, `doctor`, `receive`, `tunnel setup`) — see
185
+ The package also ships the `lacode` CLI (`notify`, `forward`, `doctor`, `version`,
186
+ `update`, `receive`, `tunnel setup`) — see
186
187
  [Remote hosts](../README.md#remote-hosts). The plugin install does **not** put it on your
187
188
  `PATH`; run it from the package's `node_modules/.bin`, or install just the CLI
188
189
  globally with `npm i -g @lawaty/lacode` if you want the bare command. (That
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lawaty/lacode",
3
- "version": "0.5.0",
3
+ "version": "0.6.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",
@@ -60,5 +60,8 @@
60
60
  },
61
61
  "peerDependencies": {
62
62
  "@opencode-ai/plugin": "1.18.34"
63
+ },
64
+ "publishConfig": {
65
+ "tag": "beta"
63
66
  }
64
67
  }