@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 +23 -1
- package/README.md +17 -4
- package/dist/commands.js +79 -0
- package/dist/index.js +30 -0
- package/docs/INSTALL.md +2 -1
- package/package.json +4 -1
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.
|
|
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
|
-
|
|
182
|
-
`/lacode-doctor` and `/
|
|
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
|
|
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`, `
|
|
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.
|
|
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
|
}
|