@lawaty/lacode 0.5.0 → 0.7.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 +45 -1
- package/README.md +59 -53
- package/dist/agents.js +69 -78
- package/dist/commands.js +90 -63
- package/dist/index.js +118 -194
- package/docs/BENCHMARKS.md +7 -9
- package/docs/CONFIGURATION.md +12 -12
- package/docs/CONTEXT.md +39 -26
- package/docs/INSTALL.md +16 -16
- package/docs/OMNIROUTE.md +12 -25
- package/docs/TUNNEL.md +16 -14
- package/docs/USER-STORIES.md +25 -26
- package/package.json +4 -1
package/dist/index.js
CHANGED
|
@@ -611,8 +611,9 @@ function coolProvider(s, provider, until, reason) {
|
|
|
611
611
|
return affected;
|
|
612
612
|
}
|
|
613
613
|
|
|
614
|
-
// src/lib/
|
|
615
|
-
var
|
|
614
|
+
// src/lib/agent-instructions.ts
|
|
615
|
+
var AGENT_INSTRUCTIONS_COMMAND = "agent-instructions";
|
|
616
|
+
var AGENT_INSTRUCTIONS_WRITER = "instructions";
|
|
616
617
|
|
|
617
618
|
// src/agents.ts
|
|
618
619
|
var POOLED_BASES = ["explore", "implement", "context"];
|
|
@@ -650,14 +651,28 @@ different number.
|
|
|
650
651
|
|
|
651
652
|
The pooled agents are the ONLY route to investigation and implementation. If the
|
|
652
653
|
pool cannot serve a spawn \u2014 every slot cooling or exhausted, spawns repeatedly
|
|
653
|
-
failing, or a runtime message saying no slot is available \u2014 that is a
|
|
654
|
-
|
|
654
|
+
failing, or a runtime message saying no slot is available \u2014 that is not a reason
|
|
655
|
+
to do the work yourself, and not a reason to stop and ask.
|
|
656
|
+
|
|
657
|
+
**Retry.** A refused or cooling slot is transient and costs you nothing but a
|
|
658
|
+
turn. Spawn the bare base again. Do not escalate a \`-N\` variant when a refusal
|
|
659
|
+
names one \u2014 the refusal means the pool has a routing problem, not that you
|
|
660
|
+
picked the wrong number.
|
|
661
|
+
|
|
662
|
+
Retry is always available and never wrong, which is why it is the default. But
|
|
663
|
+
"never wrong" is not "sufficient": looping silently against a pool with every
|
|
664
|
+
model cooling goes nowhere and burns the slot doing it. So separate the two
|
|
665
|
+
failures. **Transient** \u2014 a cooldown, a rate limit, one slot at its ceiling \u2014
|
|
666
|
+
means retry. **Persistent or structural** \u2014 every slot cooling, the same spawn
|
|
667
|
+
failing repeatedly with the same error, a malformed model id \u2014 means stop
|
|
668
|
+
retrying and *report* it: say what failed (slot, model, error) and continue
|
|
669
|
+
with whatever else the task needs, or ask the user. Surface the failure; do not
|
|
670
|
+
spin on it and do not let it end the whole task.
|
|
655
671
|
|
|
656
672
|
**Never fall back to reading, grepping, exploring or implementing on your own.**
|
|
657
|
-
Do not "just quickly" inspect a file because a spawn failed.
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
subagents. A failed spawn is never permission, and silence is not consent.
|
|
673
|
+
Do not "just quickly" inspect a file because a spawn failed. A failed spawn is
|
|
674
|
+
never permission, and silence is not consent \u2014 that part does not change with
|
|
675
|
+
cooldowns, and no amount of retrying grants it.
|
|
661
676
|
|
|
662
677
|
\`edit\`/\`write\`/\`bash\` stay yours for applying and verifying what a subagent
|
|
663
678
|
returned \u2014 never as a substitute for a spawn that could not run.
|
|
@@ -736,7 +751,12 @@ missing). Never paste context file contents into the report.`,
|
|
|
736
751
|
"edit": {
|
|
737
752
|
"*": "allow",
|
|
738
753
|
"*/.opencode/context/**": "deny",
|
|
739
|
-
".opencode/context/**": "deny"
|
|
754
|
+
".opencode/context/**": "deny",
|
|
755
|
+
// AGENTS.md has exactly one writer, the hidden `instructions` agent.
|
|
756
|
+
// opencode resolves a file permission with findLast over the map's own
|
|
757
|
+
// entry order, so this deny has to come AFTER the broad "*" allow above.
|
|
758
|
+
"AGENTS.md": "deny",
|
|
759
|
+
"*/AGENTS.md": "deny"
|
|
740
760
|
},
|
|
741
761
|
"bash": "allow",
|
|
742
762
|
"webfetch": "deny",
|
|
@@ -979,7 +999,11 @@ if the blocker is understanding rather than execution, report that instead.`,
|
|
|
979
999
|
"edit": {
|
|
980
1000
|
"*": "allow",
|
|
981
1001
|
"*/.opencode/context/**": "deny",
|
|
982
|
-
".opencode/context/**": "deny"
|
|
1002
|
+
".opencode/context/**": "deny",
|
|
1003
|
+
// AGENTS.md is written by the hidden `instructions` agent alone; LAST
|
|
1004
|
+
// match wins, so this deny must sit after the broad allow above.
|
|
1005
|
+
"AGENTS.md": "deny",
|
|
1006
|
+
"*/AGENTS.md": "deny"
|
|
983
1007
|
},
|
|
984
1008
|
"bash": "allow",
|
|
985
1009
|
"webfetch": "allow",
|
|
@@ -1023,29 +1047,14 @@ You own exactly these five files and nothing else:
|
|
|
1023
1047
|
during work that are not already recorded in the project's own decision
|
|
1024
1048
|
log. Concise; variable length.
|
|
1025
1049
|
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
You never create, edit, rename, or delete it \u2014 not at bootstrap, not in an
|
|
1035
|
-
update, not in a review, and not when a run of your own concludes the work
|
|
1036
|
-
has moved on. The client edits it, or asks a live agent to edit it on
|
|
1037
|
-
their explicit instruction. Either way it is their hand, never yours.
|
|
1038
|
-
|
|
1039
|
-
Read it before you write anything. Its \`## Current\` and \`## Next\`
|
|
1040
|
-
sections tell you what is actually being worked on, and \`## Confirmed\`
|
|
1041
|
-
tells you which of your facts the client has ratified. A fact you are
|
|
1042
|
-
writing that appears under \`## Confirmed\` may be stated plainly, without
|
|
1043
|
-
a confidence marker; anything else follows your normal rules \u2014 infer
|
|
1044
|
-
carefully, mark uncertainty. Never copy its content into the five files.
|
|
1045
|
-
|
|
1046
|
-
A missing or empty section is not an instruction. It means the client has
|
|
1047
|
-
not said, not that you may assume anything \u2014 proceed exactly as you would
|
|
1048
|
-
without the file.
|
|
1050
|
+
# Current-state only
|
|
1051
|
+
|
|
1052
|
+
Every file you write states only the current arrangement and the reason it
|
|
1053
|
+
holds, in the present tense. No "used to", "previously", "before this",
|
|
1054
|
+
migration notes or deprecation narrative \u2014 history belongs in the project's
|
|
1055
|
+
changelog, not in your map. An absence that is true today is a fact worth
|
|
1056
|
+
stating, not a removal worth narrating. When a past state is worth keeping,
|
|
1057
|
+
record it briefly in \`changelog.md\` instead.
|
|
1049
1058
|
|
|
1050
1059
|
Never create, modify, or delete anything outside \`.opencode/context/\`. The
|
|
1051
1060
|
\`edit\` permission enforces this. Do not attempt to work around it with bash.
|
|
@@ -1081,11 +1090,6 @@ Then write the five files. For each significant subsystem, give a short
|
|
|
1081
1090
|
reason. This is the single most valuable thing you produce: where an agent
|
|
1082
1091
|
should open its editor next.
|
|
1083
1092
|
|
|
1084
|
-
manager.md is created for the client by the plugin, not by you; if it is
|
|
1085
|
-
missing anyway, mention once in your final report that they can add it
|
|
1086
|
-
themselves to record what is being worked on, what comes next, and what
|
|
1087
|
-
they have ratified. Never create it.
|
|
1088
|
-
|
|
1089
1093
|
# Incremental update
|
|
1090
1094
|
|
|
1091
1095
|
Trigger: \`.opencode/context/\` exists and work has just been done here.
|
|
@@ -1113,14 +1117,6 @@ that is already stated in another context file \u2014 one fact has one home, and
|
|
|
1113
1117
|
the other files link to it. If nothing material changed, make no edits and
|
|
1114
1118
|
report "no update needed".
|
|
1115
1119
|
|
|
1116
|
-
Before editing, re-read manager.md: its \`## Current\` and \`## Next\`
|
|
1117
|
-
sections tell you what the client is actually working on, and its
|
|
1118
|
-
\`## Confirmed\` section settles which of your facts are ratified. If what
|
|
1119
|
-
you are about to write would place the code somewhere the client never
|
|
1120
|
-
described, trust the code, make the edit the code justifies, and report
|
|
1121
|
-
the gap in your final message \u2014 never reconcile the two sides silently,
|
|
1122
|
-
and never edit manager.md to close it.
|
|
1123
|
-
|
|
1124
1120
|
# Minimal exploration
|
|
1125
1121
|
|
|
1126
1122
|
Default shape:
|
|
@@ -1215,14 +1211,10 @@ Audit the five files for:
|
|
|
1215
1211
|
- duplicated information \u2014 one canonical place per fact
|
|
1216
1212
|
- missing canonical entry points for significant subsystems
|
|
1217
1213
|
- unsupported assumptions \u2014 anything unmarked that you cannot verify
|
|
1218
|
-
- manager.md, if present: is \`## Current\` and \`## Next\` still true of
|
|
1219
|
-
recent work, is every \`## Confirmed\` entry still true of the code, has
|
|
1220
|
-
it outgrown roughly 150 lines \u2014 report findings, but never edit, prune,
|
|
1221
|
-
or reorganize it; that is the client's call
|
|
1222
1214
|
|
|
1223
|
-
Fix problems only in the five files you own
|
|
1224
|
-
|
|
1225
|
-
|
|
1215
|
+
Fix problems only in the five files you own. Never modify source code
|
|
1216
|
+
during a review. Report what you found, what you fixed, and what you
|
|
1217
|
+
could not verify.
|
|
1226
1218
|
|
|
1227
1219
|
# Reporting
|
|
1228
1220
|
|
|
@@ -1240,9 +1232,7 @@ Finish with a short report:
|
|
|
1240
1232
|
"edit": {
|
|
1241
1233
|
"*": "deny",
|
|
1242
1234
|
"*/.opencode/context/**": "allow",
|
|
1243
|
-
".opencode/context/**": "allow"
|
|
1244
|
-
"*/.opencode/context/manager.md": "deny",
|
|
1245
|
-
".opencode/context/manager.md": "deny"
|
|
1235
|
+
".opencode/context/**": "allow"
|
|
1246
1236
|
},
|
|
1247
1237
|
"bash": {
|
|
1248
1238
|
"*": "deny",
|
|
@@ -1290,58 +1280,60 @@ Finish with a short report:
|
|
|
1290
1280
|
}
|
|
1291
1281
|
}
|
|
1292
1282
|
};
|
|
1293
|
-
var
|
|
1294
|
-
description: "
|
|
1283
|
+
var INSTRUCTIONS_AGENT = {
|
|
1284
|
+
description: "Edits the project's AGENTS.md. Run only by the /agent-instructions command; no agent or subagent can spawn it.",
|
|
1295
1285
|
mode: "subagent",
|
|
1296
1286
|
hidden: true,
|
|
1297
1287
|
temperature: 0.1,
|
|
1298
1288
|
prompt: `# Role
|
|
1299
1289
|
|
|
1300
|
-
You maintain one file and nothing else:
|
|
1301
|
-
|
|
1302
|
-
other path.
|
|
1290
|
+
You maintain one file and nothing else: the project's \`AGENTS.md\`. You are not a
|
|
1291
|
+
coding agent and you touch no other path.
|
|
1303
1292
|
|
|
1304
|
-
You are run only by the \`/
|
|
1293
|
+
You are run only by the \`/agent-instructions\` command. The client tells you what
|
|
1305
1294
|
they want recorded or changed in the file; you apply that to the file and report
|
|
1306
1295
|
what you changed. You have no permission to edit anything else, and you need
|
|
1307
1296
|
none.
|
|
1308
1297
|
|
|
1309
1298
|
# The file
|
|
1310
1299
|
|
|
1311
|
-
|
|
1312
|
-
|
|
1313
|
-
|
|
1314
|
-
|
|
1300
|
+
\`AGENTS.md\` is the project's standing instructions \u2014 the rules every agent and
|
|
1301
|
+
subagent runs under. opencode loads it into every session automatically, so what
|
|
1302
|
+
you write here steers all work in this project. Keep it about how to work:
|
|
1303
|
+
conventions, constraints, priorities, things to always do and never do. It says
|
|
1304
|
+
nothing about where code lives \u2014 the source and the context map are the authority
|
|
1305
|
+
on that.
|
|
1315
1306
|
|
|
1316
1307
|
# How to apply a directive
|
|
1317
1308
|
|
|
1318
|
-
- Do what the client asked
|
|
1319
|
-
|
|
1320
|
-
|
|
1309
|
+
- Do what the client asked: add, rewrite, move or remove exactly what they asked
|
|
1310
|
+
for. A bare sentence goes under the most fitting existing heading, and you
|
|
1311
|
+
create a heading only if none fits.
|
|
1321
1312
|
- Never invent facts, never record your own inference as the client's, and never
|
|
1322
1313
|
tidy, reorder or delete content the client did not ask you to touch.
|
|
1323
|
-
-
|
|
1324
|
-
history.
|
|
1314
|
+
- Prefer current-state wording: state how things are, not how they changed.
|
|
1325
1315
|
- Keep the file concise. If it is growing past roughly 150 lines, say so.
|
|
1316
|
+
- If \`AGENTS.md\` does not exist, stop and report that there is nothing to edit.
|
|
1317
|
+
Do not create it.
|
|
1326
1318
|
|
|
1327
1319
|
# Reporting
|
|
1328
1320
|
|
|
1329
1321
|
Report briefly what you changed and where. Do not edit any other file.`,
|
|
1330
1322
|
permission: {
|
|
1331
|
-
// Read is scoped to the one file too: the directive is about
|
|
1323
|
+
// Read is scoped to the one file too: the directive is about AGENTS.md, and
|
|
1332
1324
|
// nothing else is any of this agent's business.
|
|
1333
1325
|
read: {
|
|
1334
1326
|
"*": "deny",
|
|
1335
|
-
".
|
|
1336
|
-
"
|
|
1327
|
+
"AGENTS.md": "allow",
|
|
1328
|
+
"*/AGENTS.md": "allow"
|
|
1337
1329
|
},
|
|
1338
1330
|
glob: "deny",
|
|
1339
1331
|
grep: "deny",
|
|
1340
1332
|
list: "deny",
|
|
1341
1333
|
edit: {
|
|
1342
1334
|
"*": "deny",
|
|
1343
|
-
".
|
|
1344
|
-
"
|
|
1335
|
+
"AGENTS.md": "allow",
|
|
1336
|
+
"*/AGENTS.md": "allow"
|
|
1345
1337
|
},
|
|
1346
1338
|
bash: "deny",
|
|
1347
1339
|
webfetch: "deny",
|
|
@@ -1367,7 +1359,7 @@ function buildAgentSet() {
|
|
|
1367
1359
|
set[`${base}-${slot}`] = variant;
|
|
1368
1360
|
}
|
|
1369
1361
|
}
|
|
1370
|
-
set[
|
|
1362
|
+
set[AGENT_INSTRUCTIONS_WRITER] = INSTRUCTIONS_AGENT;
|
|
1371
1363
|
return set;
|
|
1372
1364
|
}
|
|
1373
1365
|
function applyAgents(cfg, agents = buildAgentSet(), opts = {}) {
|
|
@@ -1407,13 +1399,7 @@ Remember: write conclusions, not inventories. Link to the project's existing
|
|
|
1407
1399
|
documentation instead of duplicating it.
|
|
1408
1400
|
|
|
1409
1401
|
Report what you created or updated, what you deliberately left out, and
|
|
1410
|
-
anything that contradicts the code or existing docs
|
|
1411
|
-
|
|
1412
|
-
If \`.opencode/context/manager.md\` does not exist, mention in your final
|
|
1413
|
-
report that the client can add it themselves \u2014 their own file with
|
|
1414
|
-
\`## Current\`, \`## Next\`, \`## Confirmed\` and \`## Instructions\` \u2014 for what
|
|
1415
|
-
is being worked on, what comes next, what they have ratified, and standing
|
|
1416
|
-
direction. Never create it.`
|
|
1402
|
+
anything that contradicts the code or existing docs.`
|
|
1417
1403
|
},
|
|
1418
1404
|
"context-update": {
|
|
1419
1405
|
description: "Incrementally update .opencode/context/ after recent work via the context agent.",
|
|
@@ -1444,9 +1430,8 @@ timestamp-only edits. If nothing material changed, make no edits and report
|
|
|
1444
1430
|
subtask: true,
|
|
1445
1431
|
template: `Context review.
|
|
1446
1432
|
|
|
1447
|
-
Everything outside the five context files is read-only for this run
|
|
1448
|
-
|
|
1449
|
-
code under any circumstances.
|
|
1433
|
+
Everything outside the five context files is read-only for this run. Do not
|
|
1434
|
+
modify source code under any circumstances.
|
|
1450
1435
|
|
|
1451
1436
|
Audit the five context files for:
|
|
1452
1437
|
|
|
@@ -1459,18 +1444,15 @@ Audit the five context files for:
|
|
|
1459
1444
|
- missing canonical entry points for significant subsystems
|
|
1460
1445
|
- unsupported assumptions \u2014 anything stated as fact that you cannot verify,
|
|
1461
1446
|
or that lacks a confidence marker
|
|
1462
|
-
- \`manager.md\`, if present: is \`## Current\` and \`## Next\` still true of
|
|
1463
|
-
recent work, is every \`## Confirmed\` entry still true of the code, has it
|
|
1464
|
-
outgrown roughly 150 lines \u2014 report findings, but never edit it
|
|
1465
1447
|
|
|
1466
1448
|
Fix what you find in \`.opencode/context/\` only, then report: findings, fixes,
|
|
1467
1449
|
and anything you could not verify.`
|
|
1468
1450
|
},
|
|
1469
|
-
|
|
1470
|
-
description: "Apply a client instruction to .
|
|
1471
|
-
agent:
|
|
1451
|
+
[AGENT_INSTRUCTIONS_COMMAND]: {
|
|
1452
|
+
description: "Apply a client instruction to the project's AGENTS.md. Client-only \u2014 runs the instructions agent, the only writer of that file.",
|
|
1453
|
+
agent: AGENT_INSTRUCTIONS_WRITER,
|
|
1472
1454
|
subtask: true,
|
|
1473
|
-
template: `Apply this instruction to
|
|
1455
|
+
template: `Apply this instruction to the project's \`AGENTS.md\`, and edit only that file:
|
|
1474
1456
|
|
|
1475
1457
|
$ARGUMENTS
|
|
1476
1458
|
|
|
@@ -1506,6 +1488,36 @@ Print one PASS/FAIL line per check below, with the exact fix for any FAIL.
|
|
|
1506
1488
|
whether or not the gateway advertises it, so it needs no allowlist entry.
|
|
1507
1489
|
|
|
1508
1490
|
Change nothing. Print a final one-line summary of failures.`
|
|
1491
|
+
},
|
|
1492
|
+
"pool": {
|
|
1493
|
+
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.",
|
|
1494
|
+
template: `Apply this change to the LaCode pool on this machine:
|
|
1495
|
+
|
|
1496
|
+
$ARGUMENTS
|
|
1497
|
+
|
|
1498
|
+
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.
|
|
1499
|
+
|
|
1500
|
+
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.
|
|
1501
|
+
|
|
1502
|
+
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:
|
|
1503
|
+
- one slot per line: \`<provider>/<model-id> [weight]\`
|
|
1504
|
+
- at most ${MAX_SLOTS} slots, and a model may appear only once
|
|
1505
|
+
- 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.
|
|
1506
|
+
- \`#\` starts a comment (whole-line or trailing); blank lines are ignored.
|
|
1507
|
+
If the instruction names a model ambiguously, or would need more than ${MAX_SLOTS} slots, stop and ask rather than guessing.
|
|
1508
|
+
|
|
1509
|
+
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.
|
|
1510
|
+
|
|
1511
|
+
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.
|
|
1512
|
+
|
|
1513
|
+
Two traps that fail silently, so check and state them explicitly:
|
|
1514
|
+
- 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.
|
|
1515
|
+
- 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\`.
|
|
1516
|
+
- A provider \`whitelist\` is an allowlist, and the plugin appends your model to it at config time. Nothing for you to do there.
|
|
1517
|
+
|
|
1518
|
+
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.
|
|
1519
|
+
|
|
1520
|
+
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
1521
|
}
|
|
1510
1522
|
};
|
|
1511
1523
|
function applyCommands(cfg, commands = COMMANDS) {
|
|
@@ -1522,45 +1534,6 @@ function applyCommands(cfg, commands = COMMANDS) {
|
|
|
1522
1534
|
}
|
|
1523
1535
|
return { added, kept };
|
|
1524
1536
|
}
|
|
1525
|
-
var INSTRUCTION_MARKER = "<!-- lacode:context-map-instruction:v1 -->";
|
|
1526
|
-
var INSTRUCTION_OPT_OUT = "<!-- lacode:context-map-instruction:off -->";
|
|
1527
|
-
var STANDING_INSTRUCTION = `## Repository context map
|
|
1528
|
-
|
|
1529
|
-
This project may keep a navigation map in \`.opencode/context/\`. Before broad
|
|
1530
|
-
exploration, check whether it exists (\`ls .opencode/context\` \u2014 never the \`glob\`
|
|
1531
|
-
tool, which skips hidden directories). If \`manager.md\` exists there, read it
|
|
1532
|
-
first: it is the client's own statement of intent \u2014 what is being worked on,
|
|
1533
|
-
what comes next, \`## Confirmed\` lists what the client has ratified, and
|
|
1534
|
-
\`## Instructions\` holds standing managerial directions. It outranks every
|
|
1535
|
-
other context file and every inference of yours about what matters right now.
|
|
1536
|
-
It says nothing about where code lives, so it cannot contradict the code;
|
|
1537
|
-
treat its \`## Confirmed\` entries as settled decisions, not as a licence to
|
|
1538
|
-
skip reading the code. Any heading in it is direction, so read the whole
|
|
1539
|
-
file rather than only the sections above. The rest of the map remains usable,
|
|
1540
|
-
just unratified where \`## Confirmed\` does not cover it. Then read only the
|
|
1541
|
-
file relevant to the task \u2014 \`architecture.md\`, \`contexts.md\`,
|
|
1542
|
-
\`conventions.md\`, \`workflows.md\`, \`decisions.md\` \u2014 and start from the
|
|
1543
|
-
canonical entry points it lists.
|
|
1544
|
-
|
|
1545
|
-
Source code is authoritative: if the map disagrees with the code,
|
|
1546
|
-
trust the code and say so; never edit source to match it.
|
|
1547
|
-
Never edit the five context files yourself \u2014 the context agent owns them.
|
|
1548
|
-
\`manager.md\` is the client's own file and the context agent is explicitly
|
|
1549
|
-
permission-denied from writing it. You may write it only when the client
|
|
1550
|
-
explicitly asks you to in this conversation, and then only what they asked
|
|
1551
|
-
for; never on your own initiative, and never to "keep it up to date". Say in
|
|
1552
|
-
your final report if your work revealed something that belongs to the map, or
|
|
1553
|
-
if \`manager.md\` and the code disagree.`;
|
|
1554
|
-
function applyInstructions(cfg) {
|
|
1555
|
-
const existing = Array.isArray(cfg.instructions) ? cfg.instructions : [];
|
|
1556
|
-
const texts = existing.filter((e) => typeof e === "string");
|
|
1557
|
-
if (texts.some((e) => e.includes(INSTRUCTION_OPT_OUT))) return { added: false };
|
|
1558
|
-
if (texts.some((e) => e.includes(INSTRUCTION_MARKER))) return { added: false };
|
|
1559
|
-
cfg.instructions = [...existing, `${INSTRUCTION_MARKER}
|
|
1560
|
-
|
|
1561
|
-
${STANDING_INSTRUCTION}`];
|
|
1562
|
-
return { added: true };
|
|
1563
|
-
}
|
|
1564
1537
|
|
|
1565
1538
|
// src/plugins/agent-pool.ts
|
|
1566
1539
|
import { tool } from "@opencode-ai/plugin";
|
|
@@ -1896,7 +1869,7 @@ round robin in slot order, each slot taking as many turns as its weight says, so
|
|
|
1896
1869
|
});
|
|
1897
1870
|
|
|
1898
1871
|
// src/plugins/context-autoupdate.ts
|
|
1899
|
-
import { existsSync as existsSync2, readdirSync as readdirSync2, statSync as statSync2
|
|
1872
|
+
import { existsSync as existsSync2, readdirSync as readdirSync2, statSync as statSync2 } from "node:fs";
|
|
1900
1873
|
import * as path2 from "node:path";
|
|
1901
1874
|
|
|
1902
1875
|
// src/lib/jsonc.ts
|
|
@@ -1951,7 +1924,7 @@ import { readFileSync as readFileSync3 } from "node:fs";
|
|
|
1951
1924
|
import * as path from "node:path";
|
|
1952
1925
|
var CONTEXT_DIR = path.join(".opencode", "context");
|
|
1953
1926
|
var WRITER = "context";
|
|
1954
|
-
var
|
|
1927
|
+
var INSTRUCTIONS_WRITER = AGENT_INSTRUCTIONS_WRITER;
|
|
1955
1928
|
var toPosix = (value) => value.split(path.sep).join("/");
|
|
1956
1929
|
var compilePattern = (pattern) => {
|
|
1957
1930
|
const escaped = toPosix(pattern).replace(/[.+^${}()|[\]\\]/g, "\\$&").replace(/\*/g, ".*").replace(/\?/g, ".");
|
|
@@ -1980,8 +1953,8 @@ var evaluatedForm = (mapDir, projectRoot) => {
|
|
|
1980
1953
|
const absolute = toPosix(path.join(mapDir, "architecture.md"));
|
|
1981
1954
|
return projectRoot ? toPosix(path.relative(projectRoot, absolute)) : absolute;
|
|
1982
1955
|
};
|
|
1983
|
-
var
|
|
1984
|
-
const absolute = toPosix(path.join(mapDir, "
|
|
1956
|
+
var agentsForm = (mapDir, projectRoot) => {
|
|
1957
|
+
const absolute = toPosix(path.join(mapDir, "..", "..", "AGENTS.md"));
|
|
1985
1958
|
return projectRoot ? toPosix(path.relative(projectRoot, absolute)) : absolute;
|
|
1986
1959
|
};
|
|
1987
1960
|
var verifyWriterRule = (configPath, mapDir, projectRoot) => {
|
|
@@ -2015,17 +1988,17 @@ var verifyWriterRule = (configPath, mapDir, projectRoot) => {
|
|
|
2015
1988
|
return `context map has more than one writer: ${offenders.join(", ")} would be granted the map by permission, evaluating "${form}". Only ${WRITER} may write the map. Narrow the grant and put the deny LAST -- opencode takes the LAST matching rule, so "${CONTEXT_DIR}/**": "deny" written after "*": "allow" is what denies it: "*": "allow", "*/${CONTEXT_DIR}/**": "deny", "${CONTEXT_DIR}/**": "deny". Both spellings are required because the evaluated path depends on the project root.`;
|
|
2016
1989
|
}
|
|
2017
1990
|
}
|
|
2018
|
-
const
|
|
1991
|
+
const agentsPath = agentsForm(mapDir, projectRoot);
|
|
2019
1992
|
const agentDefs = readAgents(raw);
|
|
2020
1993
|
if (agentDefs) {
|
|
2021
1994
|
const writers = [];
|
|
2022
1995
|
for (const [name, def] of Object.entries(agentDefs)) {
|
|
2023
|
-
if (name ===
|
|
2024
|
-
const win = winningRule(permissionOf(def),
|
|
1996
|
+
if (name === INSTRUCTIONS_WRITER) continue;
|
|
1997
|
+
const win = winningRule(permissionOf(def), agentsPath);
|
|
2025
1998
|
if (win?.action === "allow") writers.push(`${name} (edit rule "${win.pattern}": "${win.action}")`);
|
|
2026
1999
|
}
|
|
2027
2000
|
if (writers.length > 0) {
|
|
2028
|
-
return
|
|
2001
|
+
return `AGENTS.md is writable by permission: ${writers.join(", ")}. It is the project's own file \u2014 the standing instructions every agent runs under \u2014 and only the \`${INSTRUCTIONS_WRITER}\` agent (run solely by /agent-instructions) may write it. Deny it LAST on every other agent, after any broad allow, because opencode takes the LAST matching rule: "*": "allow", "AGENTS.md": "deny", "*/AGENTS.md": "deny". Declare this on every agent above, including \`${WRITER}\`: an agent you define yourself replaces the shipped one wholesale, permission and all.`;
|
|
2029
2002
|
}
|
|
2030
2003
|
}
|
|
2031
2004
|
return void 0;
|
|
@@ -2054,51 +2027,6 @@ var IGNORED_SEGMENTS = /* @__PURE__ */ new Set([
|
|
|
2054
2027
|
".cache",
|
|
2055
2028
|
".git"
|
|
2056
2029
|
]);
|
|
2057
|
-
var MANAGER_TEMPLATE = `# Managerial directions
|
|
2058
|
-
|
|
2059
|
-
This file is yours. No agent writes it: the context agent is explicitly denied
|
|
2060
|
-
permission to touch this path, and nothing changes it except you, or an agent
|
|
2061
|
-
acting on your explicit instruction in that conversation.
|
|
2062
|
-
|
|
2063
|
-
It records what you want done and how you want it approached \u2014 what is being
|
|
2064
|
-
worked on, what comes next, what you have settled, and what every agent should
|
|
2065
|
-
keep in mind. It deliberately says nothing about where code lives: the source
|
|
2066
|
-
and the five generated context files are the only authority on that, so this
|
|
2067
|
-
file can never contradict the code.
|
|
2068
|
-
|
|
2069
|
-
Add any headings you like. The four below are a convenience, not a schema \u2014
|
|
2070
|
-
every heading in this file is direction.
|
|
2071
|
-
|
|
2072
|
-
## Current
|
|
2073
|
-
|
|
2074
|
-
<!-- What is being worked on right now. Rewrite it; do not append a history. -->
|
|
2075
|
-
|
|
2076
|
-
## Next
|
|
2077
|
-
|
|
2078
|
-
<!-- What comes next, in order. -->
|
|
2079
|
-
|
|
2080
|
-
## Confirmed
|
|
2081
|
-
|
|
2082
|
-
<!-- What you have personally ratified. Nothing enters or leaves this list
|
|
2083
|
-
except by your hand \u2014 no agent promotes, prunes or rewrites it. -->
|
|
2084
|
-
|
|
2085
|
-
## Instructions
|
|
2086
|
-
|
|
2087
|
-
<!-- Standing direction that applies beyond the current task: priorities, things
|
|
2088
|
-
to care about, constraints that are not obvious from the code. -->`;
|
|
2089
|
-
var ensureManagerFile = (root) => {
|
|
2090
|
-
try {
|
|
2091
|
-
const target = path2.join(root, ".opencode", "context", "manager.md");
|
|
2092
|
-
if (existsSync2(target)) return { created: false };
|
|
2093
|
-
mkdirSync2(path2.dirname(target), { recursive: true });
|
|
2094
|
-
writeFileSync2(target, MANAGER_TEMPLATE, { flag: "wx" });
|
|
2095
|
-
return { created: true };
|
|
2096
|
-
} catch (error) {
|
|
2097
|
-
if (error?.code === "EEXIST") return { created: false };
|
|
2098
|
-
console.warn(`[context-autoupdate] ensureManagerFile failed: ${error}`);
|
|
2099
|
-
return { created: false };
|
|
2100
|
-
}
|
|
2101
|
-
};
|
|
2102
2030
|
var toPosix2 = (value) => value.split(path2.sep).join("/");
|
|
2103
2031
|
var findProjectConfig = () => {
|
|
2104
2032
|
let dir = import.meta.dir ?? path2.dirname(new URL(import.meta.url).pathname);
|
|
@@ -2142,7 +2070,6 @@ var contextAutoUpdateHooks = (async ({ client, directory }, options) => {
|
|
|
2142
2070
|
});
|
|
2143
2071
|
const root = path2.resolve(directory);
|
|
2144
2072
|
const contextAbs = path2.join(root, CONTEXT_DIR2);
|
|
2145
|
-
if (ensureManagerFile(root).created) void log("info", `created ${CONTEXT_DIR2}/manager.md`);
|
|
2146
2073
|
const config = findProjectConfig();
|
|
2147
2074
|
const writerProblem = config ? verifyWriterRule(config, contextAbs, root) : void 0;
|
|
2148
2075
|
if (writerProblem) void log("error", writerProblem);
|
|
@@ -3102,7 +3029,7 @@ var omniRouteHooks = async (input, options, deps = {}) => {
|
|
|
3102
3029
|
|
|
3103
3030
|
// src/lib/selfupdate.ts
|
|
3104
3031
|
import { spawn as spawn2 } from "node:child_process";
|
|
3105
|
-
import { closeSync, mkdirSync as
|
|
3032
|
+
import { closeSync, mkdirSync as mkdirSync2, openSync, readFileSync as readFileSync4, renameSync as renameSync2, rmSync, statSync as statSync3, unlinkSync as unlinkSync2, writeFileSync as writeFileSync2, writeSync } from "node:fs";
|
|
3106
3033
|
import { homedir as homedir2 } from "node:os";
|
|
3107
3034
|
import { dirname as dirname3, join as join6, resolve as resolve2 } from "node:path";
|
|
3108
3035
|
var REGISTRY_LATEST = `https://registry.npmjs.org/${PACKAGE_NAME}/latest`;
|
|
@@ -3196,8 +3123,8 @@ function readState(path4 = STATE_PATH) {
|
|
|
3196
3123
|
}
|
|
3197
3124
|
function writeState(state2, path4 = STATE_PATH) {
|
|
3198
3125
|
try {
|
|
3199
|
-
|
|
3200
|
-
|
|
3126
|
+
mkdirSync2(dirname3(path4), { recursive: true });
|
|
3127
|
+
writeFileSync2(path4, JSON.stringify(state2));
|
|
3201
3128
|
} catch {
|
|
3202
3129
|
}
|
|
3203
3130
|
}
|
|
@@ -3223,7 +3150,7 @@ function acquireLock(path4 = LOCK_PATH, now = Date.now()) {
|
|
|
3223
3150
|
};
|
|
3224
3151
|
};
|
|
3225
3152
|
try {
|
|
3226
|
-
|
|
3153
|
+
mkdirSync2(dirname3(path4), { recursive: true });
|
|
3227
3154
|
return claim();
|
|
3228
3155
|
} catch {
|
|
3229
3156
|
let orphaned = false;
|
|
@@ -3411,9 +3338,6 @@ var LaCode = (async (input, options) => {
|
|
|
3411
3338
|
if (cmds.added.length > 0) {
|
|
3412
3339
|
await log("info", `commands: added ${cmds.added.join(", ")}; kept ${cmds.kept.join(", ") || "none"} already defined`);
|
|
3413
3340
|
}
|
|
3414
|
-
if (applyInstructions(cfg).added) {
|
|
3415
|
-
await log("info", "instructions: ensured the standing context-map rule is present");
|
|
3416
|
-
}
|
|
3417
3341
|
await omniroute.config?.(cfg);
|
|
3418
3342
|
await pool.config?.(cfg);
|
|
3419
3343
|
} catch (error) {
|
package/docs/BENCHMARKS.md
CHANGED
|
@@ -88,10 +88,9 @@ never priced at 0 and never priced at a guess. If you name such a model
|
|
|
88
88
|
explicitly with `--model` / `--baseline-model`, the run exits non-zero and tells
|
|
89
89
|
you to refresh or to pass `--allow-unknown`.
|
|
90
90
|
|
|
91
|
-
This is
|
|
92
|
-
|
|
93
|
-
default offline run cannot produce an honest ratio.
|
|
94
|
-
as designed.
|
|
91
|
+
This is why the results table below is empty. The pinned snapshot covers the
|
|
92
|
+
bundled free preset only; the default baseline model is not in it, so the
|
|
93
|
+
default offline run cannot produce an honest ratio.
|
|
95
94
|
|
|
96
95
|
## Results
|
|
97
96
|
|
|
@@ -116,11 +115,10 @@ harness prints `unknown`.
|
|
|
116
115
|
`--out docs/BENCHMARKS.md` and exits 2.
|
|
117
116
|
|
|
118
117
|
That refusal is the point. This file ships inside the npm tarball (`files`
|
|
119
|
-
includes `docs`)
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
footer pointing back here — goes to its own gitignored filename.
|
|
118
|
+
includes `docs`) and is the hand-written explainer. A generated table is a
|
|
119
|
+
measurement of one machine on one day, not a claim about the package, so the
|
|
120
|
+
explainer is protected and the generated output — which carries a footer
|
|
121
|
+
pointing back here — goes to its own gitignored filename.
|
|
124
122
|
|
|
125
123
|
```bash
|
|
126
124
|
npm run benchmark -- --out docs/BENCHMARKS.generated.md
|
package/docs/CONFIGURATION.md
CHANGED
|
@@ -43,10 +43,10 @@ writes the context map and has no business in a browser.
|
|
|
43
43
|
opencode folds an agent's `tools` into its permission list **at config-load
|
|
44
44
|
time**, while decoding the config — before any plugin runs. A plugin's `config`
|
|
45
45
|
hook mutates the already-decoded config, so the fold never re-runs and anything
|
|
46
|
-
it puts in `agent.tools` is inert.
|
|
47
|
-
|
|
48
|
-
`
|
|
49
|
-
|
|
46
|
+
it puts in `agent.tools` is inert. On opencode 1.18.34/1.18.35, a grant
|
|
47
|
+
written in your own config shows two `playwright_*` entries — `deny` then
|
|
48
|
+
`allow`, last one winning — while the same grant injected by the plugin shows
|
|
49
|
+
only the `deny`. LaCode's other injected
|
|
50
50
|
fields are not affected; `permission`, `prompt`, `model`, `description`,
|
|
51
51
|
`temperature`, `mode` and `hidden` are all read after the plugin hook runs.
|
|
52
52
|
|
|
@@ -78,8 +78,8 @@ process.
|
|
|
78
78
|
**18 agent keys**: the 5 bases `build`, `plan`, `explore`, `implement`,
|
|
79
79
|
`context`, plus one hidden
|
|
80
80
|
`<base>-1`..`-4` pool variant for each of the 3 pooled bases, plus the hidden
|
|
81
|
-
`
|
|
82
|
-
only from the `/
|
|
81
|
+
`instructions` agent — the one writer of the project's root `AGENTS.md`,
|
|
82
|
+
reachable only from the `/agent-instructions` command.
|
|
83
83
|
|
|
84
84
|
`build` and `plan` exist in opencode as built-ins, so on a stock config those
|
|
85
85
|
two keys are already taken and merge-without-clobber keeps the built-in — the
|
|
@@ -92,13 +92,13 @@ duplicate of `plan` apart from a pinned model, and `plan` is injected instead.
|
|
|
92
92
|
description — a package cannot ship someone else's provider, quota or benchmark
|
|
93
93
|
results — though its escalation ladder still resolves, because the two deep
|
|
94
94
|
tiers it escalates to *are* injected. And `explore`, `general` and `implement`
|
|
95
|
-
are only `{ "disable": true }` stubs
|
|
96
|
-
|
|
97
|
-
|
|
95
|
+
are only `{ "disable": true }` stubs: turning off opencode's built-in agents
|
|
96
|
+
is a decision about one person's configuration, not a default a package should
|
|
97
|
+
impose on a stranger.
|
|
98
98
|
|
|
99
|
-
One **field** is deliberately not injected
|
|
100
|
-
|
|
101
|
-
|
|
99
|
+
One **field** is deliberately not injected: `tools`, for the load-time-folding
|
|
100
|
+
reason explained above. Nothing else the plugin writes into an agent is subject
|
|
101
|
+
to it.
|
|
102
102
|
|
|
103
103
|
**Installation merges, it does not clobber** (US-16). An agent, command or
|
|
104
104
|
instruction is added only when that key is absent from your config. A
|