@lawaty/lacode 0.4.1 → 0.5.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 +44 -1
- package/README.md +130 -551
- package/dist/agents.js +115 -3
- package/dist/commands.js +62 -15
- package/dist/index.js +326 -62
- package/dist/lacode.js +253 -11
- package/dist/pool.js +17 -6
- package/docs/CONFIGURATION.md +113 -0
- package/docs/INSTALL.md +209 -0
- package/docs/OMNIROUTE.md +232 -0
- package/package.json +1 -1
- package/presets/README.md +2 -2
- package/presets/free-tier.json +4 -4
package/dist/index.js
CHANGED
|
@@ -30,6 +30,7 @@ var ORPHAN_TMP_MS = 60 * 1e3;
|
|
|
30
30
|
var CATALOG_TTL_MS = 10 * 60 * 1e3;
|
|
31
31
|
var STUCK_MIN_AGE_MS = 5 * 60 * 1e3;
|
|
32
32
|
var STUCK_IDLE_MS = 3 * 60 * 1e3;
|
|
33
|
+
var MAX_CLAIM_AGE_MS = 30 * 60 * 1e3;
|
|
33
34
|
var HANG_COOLDOWN_MS = 10 * 60 * 1e3;
|
|
34
35
|
var UNAVAILABLE_COOLDOWN_MS = 60 * 1e3;
|
|
35
36
|
var MAX_WEIGHT = 100;
|
|
@@ -48,9 +49,9 @@ var USER_MODELS_PATH = join2(USER_CONFIG_DIR, "pool.json");
|
|
|
48
49
|
var USER_MODELS_TXT = join2(USER_CONFIG_DIR, "pool.txt");
|
|
49
50
|
var FALLBACK_SLOTS = [
|
|
50
51
|
{ index: 1, model: "opencode/space-bunny-free", weight: 3 },
|
|
51
|
-
{ index: 2, model: "opencode-go/
|
|
52
|
+
{ index: 2, model: "opencode-go/longcat-2.5-preview-free", weight: 2 },
|
|
52
53
|
{ index: 3, model: "opencode/big-pickle", weight: 1 },
|
|
53
|
-
{ index: 4, model: "opencode/
|
|
54
|
+
{ index: 4, model: "opencode/longcat-2.5-preview-free", weight: 1 }
|
|
54
55
|
];
|
|
55
56
|
function parseSlots(parsed, origin) {
|
|
56
57
|
const problems = [];
|
|
@@ -203,6 +204,8 @@ var LIMIT_PATTERNS = [
|
|
|
203
204
|
];
|
|
204
205
|
var isLimitText = (text) => LIMIT_PATTERNS.some((re) => re.test(text));
|
|
205
206
|
var isUnavailableText = (text) => UNAVAILABLE_PATTERN.test(text);
|
|
207
|
+
var TRANSIENT_GATEWAY_PATTERNS = [/queue budget/i, /exceeded queue/i, /aborted due to timeout/i, /timeout/i];
|
|
208
|
+
var isTransientGatewayText = (text) => TRANSIENT_GATEWAY_PATTERNS.some((re) => re.test(text));
|
|
206
209
|
var statedCooldown = (text) => {
|
|
207
210
|
const hm = /resets?\s+in\s+(\d+)\s*(?:h|hr|hour)s?\s*(\d+)?\s*(?:m|min|minute)?s?/i.exec(text);
|
|
208
211
|
if (hm) return (Number(hm[1]) * 60 + (hm[2] ? Number(hm[2]) : 0)) * 60 * 1e3;
|
|
@@ -291,7 +294,7 @@ function publish(s) {
|
|
|
291
294
|
if (!s.dirReady) return;
|
|
292
295
|
const now = Date.now();
|
|
293
296
|
const payload = {};
|
|
294
|
-
for (const [key, claim] of s.own) if (now - claim.t <= CLAIM_TTL_MS) payload[key] = claim;
|
|
297
|
+
for (const [key, claim] of s.own) if (now - (claim.hb ?? claim.t) <= CLAIM_TTL_MS) payload[key] = claim;
|
|
295
298
|
try {
|
|
296
299
|
writeFileSync(join2(s.dir, `${selfFile(s.id)}.tmp`), JSON.stringify(payload));
|
|
297
300
|
renameSync(join2(s.dir, `${selfFile(s.id)}.tmp`), join2(s.dir, selfFile(s.id)));
|
|
@@ -572,12 +575,17 @@ function idleFor(s, claim, now) {
|
|
|
572
575
|
const seen = s.activity.get(target);
|
|
573
576
|
return seen ? now - seen : Infinity;
|
|
574
577
|
}
|
|
578
|
+
function condemnReason(s, claim, now = Date.now()) {
|
|
579
|
+
const age = now - claim.t;
|
|
580
|
+
if (age > MAX_CLAIM_AGE_MS) return "overran";
|
|
581
|
+
if (age > STUCK_MIN_AGE_MS && idleFor(s, claim, now) >= STUCK_IDLE_MS) return "hung";
|
|
582
|
+
return void 0;
|
|
583
|
+
}
|
|
575
584
|
function findStuck(s, now = Date.now()) {
|
|
576
585
|
const stuck = [];
|
|
577
586
|
for (const [key, claim] of s.own) {
|
|
578
|
-
|
|
579
|
-
if (
|
|
580
|
-
stuck.push({ key, claim });
|
|
587
|
+
const reason = condemnReason(s, claim, now);
|
|
588
|
+
if (reason) stuck.push({ key, claim, reason });
|
|
581
589
|
}
|
|
582
590
|
return stuck;
|
|
583
591
|
}
|
|
@@ -603,6 +611,9 @@ function coolProvider(s, provider, until, reason) {
|
|
|
603
611
|
return affected;
|
|
604
612
|
}
|
|
605
613
|
|
|
614
|
+
// src/lib/manager-directive.ts
|
|
615
|
+
var MANAGER_AGENT_NAME = "manager";
|
|
616
|
+
|
|
606
617
|
// src/agents.ts
|
|
607
618
|
var POOLED_BASES = ["explore", "implement", "context"];
|
|
608
619
|
var AGENTS = {
|
|
@@ -1012,6 +1023,30 @@ You own exactly these five files and nothing else:
|
|
|
1012
1023
|
during work that are not already recorded in the project's own decision
|
|
1013
1024
|
log. Concise; variable length.
|
|
1014
1025
|
|
|
1026
|
+
A sixth file sits in the same directory and is not yours:
|
|
1027
|
+
\`.opencode/context/manager.md\`. It is the client's own statement of intent
|
|
1028
|
+
\u2014 what is being worked on, what comes next, what they have settled, and
|
|
1029
|
+
what every agent should keep in mind. It deliberately says nothing about
|
|
1030
|
+
where code lives: your five files and the source itself are the only
|
|
1031
|
+
authority on that, and this file never contradicts them. It has no fixed
|
|
1032
|
+
schema \u2014 read the whole file, because every heading in it is direction.
|
|
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.
|
|
1049
|
+
|
|
1015
1050
|
Never create, modify, or delete anything outside \`.opencode/context/\`. The
|
|
1016
1051
|
\`edit\` permission enforces this. Do not attempt to work around it with bash.
|
|
1017
1052
|
|
|
@@ -1046,6 +1081,11 @@ Then write the five files. For each significant subsystem, give a short
|
|
|
1046
1081
|
reason. This is the single most valuable thing you produce: where an agent
|
|
1047
1082
|
should open its editor next.
|
|
1048
1083
|
|
|
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
|
+
|
|
1049
1089
|
# Incremental update
|
|
1050
1090
|
|
|
1051
1091
|
Trigger: \`.opencode/context/\` exists and work has just been done here.
|
|
@@ -1073,6 +1113,14 @@ that is already stated in another context file \u2014 one fact has one home, and
|
|
|
1073
1113
|
the other files link to it. If nothing material changed, make no edits and
|
|
1074
1114
|
report "no update needed".
|
|
1075
1115
|
|
|
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
|
+
|
|
1076
1124
|
# Minimal exploration
|
|
1077
1125
|
|
|
1078
1126
|
Default shape:
|
|
@@ -1167,9 +1215,14 @@ Audit the five files for:
|
|
|
1167
1215
|
- duplicated information \u2014 one canonical place per fact
|
|
1168
1216
|
- missing canonical entry points for significant subsystems
|
|
1169
1217
|
- 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
|
|
1170
1222
|
|
|
1171
|
-
Fix problems only in
|
|
1172
|
-
|
|
1223
|
+
Fix problems only in the five files you own \u2014 never in manager.md, which
|
|
1224
|
+
belongs to the client. Never modify source code during a review. Report
|
|
1225
|
+
what you found, what you fixed, and what you could not verify.
|
|
1173
1226
|
|
|
1174
1227
|
# Reporting
|
|
1175
1228
|
|
|
@@ -1187,7 +1240,9 @@ Finish with a short report:
|
|
|
1187
1240
|
"edit": {
|
|
1188
1241
|
"*": "deny",
|
|
1189
1242
|
"*/.opencode/context/**": "allow",
|
|
1190
|
-
".opencode/context/**": "allow"
|
|
1243
|
+
".opencode/context/**": "allow",
|
|
1244
|
+
"*/.opencode/context/manager.md": "deny",
|
|
1245
|
+
".opencode/context/manager.md": "deny"
|
|
1191
1246
|
},
|
|
1192
1247
|
"bash": {
|
|
1193
1248
|
"*": "deny",
|
|
@@ -1235,6 +1290,68 @@ Finish with a short report:
|
|
|
1235
1290
|
}
|
|
1236
1291
|
}
|
|
1237
1292
|
};
|
|
1293
|
+
var MANAGER_AGENT = {
|
|
1294
|
+
description: "Records and edits the client's .opencode/context/manager.md. Run only by the /manager-directive command; no agent or subagent can spawn it.",
|
|
1295
|
+
mode: "subagent",
|
|
1296
|
+
hidden: true,
|
|
1297
|
+
temperature: 0.1,
|
|
1298
|
+
prompt: `# Role
|
|
1299
|
+
|
|
1300
|
+
You maintain one file and nothing else: \`.opencode/context/manager.md\`, the
|
|
1301
|
+
client's own statement of intent. You are not a coding agent and you touch no
|
|
1302
|
+
other path.
|
|
1303
|
+
|
|
1304
|
+
You are run only by the \`/manager-directive\` command. The client tells you what
|
|
1305
|
+
they want recorded or changed in the file; you apply that to the file and report
|
|
1306
|
+
what you changed. You have no permission to edit anything else, and you need
|
|
1307
|
+
none.
|
|
1308
|
+
|
|
1309
|
+
# The file
|
|
1310
|
+
|
|
1311
|
+
It records what is being worked on (\`## Current\`), what comes next
|
|
1312
|
+
(\`## Next\`), what the client has ratified (\`## Confirmed\`), and standing
|
|
1313
|
+
direction (\`## Instructions\`). The client may use any headings \u2014 every heading
|
|
1314
|
+
is direction, and the four above are a convenience, not a schema.
|
|
1315
|
+
|
|
1316
|
+
# How to apply a directive
|
|
1317
|
+
|
|
1318
|
+
- Do what the client asked. "Append", "rewrite Current", "move X to Confirmed",
|
|
1319
|
+
"add a heading" all mean exactly that; a bare sentence goes under the most
|
|
1320
|
+
fitting existing heading, and you create one only if none fits.
|
|
1321
|
+
- Never invent facts, never record your own inference as the client's, and never
|
|
1322
|
+
tidy, reorder or delete content the client did not ask you to touch.
|
|
1323
|
+
- \`## Current\` is rewritten, not appended to, so it does not become a running
|
|
1324
|
+
history.
|
|
1325
|
+
- Keep the file concise. If it is growing past roughly 150 lines, say so.
|
|
1326
|
+
|
|
1327
|
+
# Reporting
|
|
1328
|
+
|
|
1329
|
+
Report briefly what you changed and where. Do not edit any other file.`,
|
|
1330
|
+
permission: {
|
|
1331
|
+
// Read is scoped to the one file too: the directive is about manager.md, and
|
|
1332
|
+
// nothing else is any of this agent's business.
|
|
1333
|
+
read: {
|
|
1334
|
+
"*": "deny",
|
|
1335
|
+
".opencode/context/manager.md": "allow",
|
|
1336
|
+
"*/.opencode/context/manager.md": "allow"
|
|
1337
|
+
},
|
|
1338
|
+
glob: "deny",
|
|
1339
|
+
grep: "deny",
|
|
1340
|
+
list: "deny",
|
|
1341
|
+
edit: {
|
|
1342
|
+
"*": "deny",
|
|
1343
|
+
".opencode/context/manager.md": "allow",
|
|
1344
|
+
"*/.opencode/context/manager.md": "allow"
|
|
1345
|
+
},
|
|
1346
|
+
bash: "deny",
|
|
1347
|
+
webfetch: "deny",
|
|
1348
|
+
websearch: "deny",
|
|
1349
|
+
question: "allow",
|
|
1350
|
+
task: {
|
|
1351
|
+
"*": "deny"
|
|
1352
|
+
}
|
|
1353
|
+
}
|
|
1354
|
+
};
|
|
1238
1355
|
function buildAgentSet() {
|
|
1239
1356
|
const set = { ...AGENTS };
|
|
1240
1357
|
for (const base of POOLED_BASES) {
|
|
@@ -1250,6 +1367,7 @@ function buildAgentSet() {
|
|
|
1250
1367
|
set[`${base}-${slot}`] = variant;
|
|
1251
1368
|
}
|
|
1252
1369
|
}
|
|
1370
|
+
set[MANAGER_AGENT_NAME] = MANAGER_AGENT;
|
|
1253
1371
|
return set;
|
|
1254
1372
|
}
|
|
1255
1373
|
function applyAgents(cfg, agents = buildAgentSet(), opts = {}) {
|
|
@@ -1289,7 +1407,13 @@ Remember: write conclusions, not inventories. Link to the project's existing
|
|
|
1289
1407
|
documentation instead of duplicating it.
|
|
1290
1408
|
|
|
1291
1409
|
Report what you created or updated, what you deliberately left out, and
|
|
1292
|
-
anything that contradicts the code or existing docs
|
|
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.`
|
|
1293
1417
|
},
|
|
1294
1418
|
"context-update": {
|
|
1295
1419
|
description: "Incrementally update .opencode/context/ after recent work via the context agent.",
|
|
@@ -1320,8 +1444,9 @@ timestamp-only edits. If nothing material changed, make no edits and report
|
|
|
1320
1444
|
subtask: true,
|
|
1321
1445
|
template: `Context review.
|
|
1322
1446
|
|
|
1323
|
-
Everything outside
|
|
1324
|
-
|
|
1447
|
+
Everything outside the five context files is read-only for this run \u2014
|
|
1448
|
+
including \`manager.md\`, which belongs to the client. Do not modify source
|
|
1449
|
+
code under any circumstances.
|
|
1325
1450
|
|
|
1326
1451
|
Audit the five context files for:
|
|
1327
1452
|
|
|
@@ -1334,9 +1459,22 @@ Audit the five context files for:
|
|
|
1334
1459
|
- missing canonical entry points for significant subsystems
|
|
1335
1460
|
- unsupported assumptions \u2014 anything stated as fact that you cannot verify,
|
|
1336
1461
|
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
|
|
1337
1465
|
|
|
1338
1466
|
Fix what you find in \`.opencode/context/\` only, then report: findings, fixes,
|
|
1339
1467
|
and anything you could not verify.`
|
|
1468
|
+
},
|
|
1469
|
+
"manager-directive": {
|
|
1470
|
+
description: "Apply a client instruction to .opencode/context/manager.md. Client-only \u2014 runs the manager agent, the only writer of that file.",
|
|
1471
|
+
agent: MANAGER_AGENT_NAME,
|
|
1472
|
+
subtask: true,
|
|
1473
|
+
template: `Apply this instruction to \`.opencode/context/manager.md\`, and edit only that file:
|
|
1474
|
+
|
|
1475
|
+
$ARGUMENTS
|
|
1476
|
+
|
|
1477
|
+
Follow the rules for the file in your system prompt, then report what you changed.`
|
|
1340
1478
|
},
|
|
1341
1479
|
"lacode-doctor": {
|
|
1342
1480
|
description: "Check the LaCode setup \u2014 pool slots, model resolution, and the OmniRoute gateway. Report only; changes nothing.",
|
|
@@ -1364,7 +1502,8 @@ Print one PASS/FAIL line per check below, with the exact fix for any FAIL.
|
|
|
1364
1502
|
trap: an id that is a valid gateway route but is NOT in the plugin's
|
|
1365
1503
|
\`omniroute.models\` allowlist fails with
|
|
1366
1504
|
\`ProviderModelNotFoundError: Model not found\`; the fix is to add that id to
|
|
1367
|
-
the \`models\` array, or
|
|
1505
|
+
the \`models\` array, or set it as \`combo\` \u2014 the combo id is registered
|
|
1506
|
+
whether or not the gateway advertises it, so it needs no allowlist entry.
|
|
1368
1507
|
|
|
1369
1508
|
Change nothing. Print a final one-line summary of failures.`
|
|
1370
1509
|
}
|
|
@@ -1383,22 +1522,43 @@ function applyCommands(cfg, commands = COMMANDS) {
|
|
|
1383
1522
|
}
|
|
1384
1523
|
return { added, kept };
|
|
1385
1524
|
}
|
|
1525
|
+
var INSTRUCTION_MARKER = "<!-- lacode:context-map-instruction:v1 -->";
|
|
1526
|
+
var INSTRUCTION_OPT_OUT = "<!-- lacode:context-map-instruction:off -->";
|
|
1386
1527
|
var STANDING_INSTRUCTION = `## Repository context map
|
|
1387
1528
|
|
|
1388
1529
|
This project may keep a navigation map in \`.opencode/context/\`. Before broad
|
|
1389
1530
|
exploration, check whether it exists (\`ls .opencode/context\` \u2014 never the \`glob\`
|
|
1390
|
-
tool, which skips hidden directories). If
|
|
1391
|
-
the
|
|
1392
|
-
|
|
1393
|
-
|
|
1394
|
-
|
|
1395
|
-
|
|
1396
|
-
|
|
1397
|
-
|
|
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.`;
|
|
1398
1554
|
function applyInstructions(cfg) {
|
|
1399
|
-
const existing = cfg.instructions;
|
|
1400
|
-
|
|
1401
|
-
|
|
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}`];
|
|
1402
1562
|
return { added: true };
|
|
1403
1563
|
}
|
|
1404
1564
|
|
|
@@ -1443,6 +1603,19 @@ var agentPoolHooks = (async ({ client, serverUrl }, options) => {
|
|
|
1443
1603
|
const strike = recordStrike(s, claim.m, "hang", HANG_FALLBACK_MS);
|
|
1444
1604
|
if (strike) await log2("warn", `cooling ${claim.m} for ${Math.round((strike.until - now) / 1e3)}s (hang)`);
|
|
1445
1605
|
};
|
|
1606
|
+
const failOver = async (sessionID, model, reason) => {
|
|
1607
|
+
if (!sessionID) return;
|
|
1608
|
+
const entry = [...s.own.entries()].find(([, claim2]) => claim2.g === sessionID);
|
|
1609
|
+
if (!entry) return;
|
|
1610
|
+
const [key, claim] = entry;
|
|
1611
|
+
const kill = await abortSession(sessionID);
|
|
1612
|
+
release(s, key);
|
|
1613
|
+
await log(
|
|
1614
|
+
"warn",
|
|
1615
|
+
kill.ok ? `limit (${reason}) on ${model}; aborted in-flight ${claim.v} session ${sessionID} to fail over now` : `limit (${reason}) on ${model}; released in-flight ${claim.v} claim after a failed abort (${kill.why})`,
|
|
1616
|
+
{ base: claim.v, model: claim.m, slot: claim.k, session: sessionID, aborted: kill.ok, reason }
|
|
1617
|
+
);
|
|
1618
|
+
};
|
|
1446
1619
|
const reapStuck = async () => {
|
|
1447
1620
|
if (s.reaping) return;
|
|
1448
1621
|
s.reaping = true;
|
|
@@ -1451,7 +1624,7 @@ var agentPoolHooks = (async ({ client, serverUrl }, options) => {
|
|
|
1451
1624
|
const stuck = findStuck(s);
|
|
1452
1625
|
if (stuck.length === 0) return;
|
|
1453
1626
|
const released = [];
|
|
1454
|
-
for (const { key, claim } of stuck) {
|
|
1627
|
+
for (const { key, claim, reason } of stuck) {
|
|
1455
1628
|
if (!claim.g) backfillChild(s, claim);
|
|
1456
1629
|
const idle = idleFor(s, claim, Date.now());
|
|
1457
1630
|
const since = Number.isFinite(idle) ? `${Math.round(idle / 1e3)}s of silence` : "no child session ever bound";
|
|
@@ -1463,7 +1636,7 @@ var agentPoolHooks = (async ({ client, serverUrl }, options) => {
|
|
|
1463
1636
|
s.reaped++;
|
|
1464
1637
|
await log(
|
|
1465
1638
|
"warn",
|
|
1466
|
-
`hung ${claim.v} on ${claim.m} after ${age}s with ${since}; aborted session ${claim.g}`,
|
|
1639
|
+
reason === "overran" ? `overran ${claim.v} on ${claim.m} after ${age}s; aborted session ${claim.g}` : `hung ${claim.v} on ${claim.m} after ${age}s with ${since}; aborted session ${claim.g}`,
|
|
1467
1640
|
{ base: claim.v, model: claim.m, slot: claim.k, session: claim.g, aborted: true }
|
|
1468
1641
|
);
|
|
1469
1642
|
await coolForHang(claim, log);
|
|
@@ -1510,7 +1683,7 @@ var agentPoolHooks = (async ({ client, serverUrl }, options) => {
|
|
|
1510
1683
|
timer.unref?.();
|
|
1511
1684
|
void log(
|
|
1512
1685
|
"info",
|
|
1513
|
-
`reaper armed: every ${Math.round(reapMs / 1e3)}s, aborts a claim older than ${Math.round(STUCK_MIN_AGE_MS / 6e4)}m whose target has been silent ${Math.round(STUCK_IDLE_MS / 6e4)}m`
|
|
1686
|
+
`reaper armed: every ${Math.round(reapMs / 1e3)}s, aborts a claim older than ${Math.round(STUCK_MIN_AGE_MS / 6e4)}m whose target has been silent ${Math.round(STUCK_IDLE_MS / 6e4)}m, or any claim past the ${Math.round(MAX_CLAIM_AGE_MS / 6e4)}m ceiling however chatty`
|
|
1514
1687
|
);
|
|
1515
1688
|
return {
|
|
1516
1689
|
config: async (cfg) => {
|
|
@@ -1558,18 +1731,21 @@ var agentPoolHooks = (async ({ client, serverUrl }, options) => {
|
|
|
1558
1731
|
try {
|
|
1559
1732
|
const props = event.properties ?? {};
|
|
1560
1733
|
if (event.type === "session.status") {
|
|
1734
|
+
const sessionID = typeof props.sessionID === "string" ? props.sessionID : void 0;
|
|
1561
1735
|
const status2 = props.status;
|
|
1562
|
-
|
|
1736
|
+
const reason2 = status2?.action?.reason ?? "";
|
|
1737
|
+
if (status2?.type === "retry" && status2.action?.provider && (reason2 === "free_tier_limit" || isLimitText(reason2))) {
|
|
1563
1738
|
const provider = status2.action.provider;
|
|
1564
1739
|
const until = typeof status2.next === "number" && status2.next > Date.now() ? status2.next : Date.now() + HANG_FALLBACK_MS;
|
|
1565
|
-
const cooled = coolProvider(s, provider, until, "
|
|
1740
|
+
const cooled = coolProvider(s, provider, until, reason2 || "rate-limited");
|
|
1566
1741
|
if (cooled.length > 0) {
|
|
1567
|
-
await log("warn", `
|
|
1742
|
+
await log("warn", `limit on provider ${provider} (${reason2 || "rate-limited"}); cooling ${cooled.length} slot(s) until ${new Date(until).toISOString().slice(11, 19)}Z`, {
|
|
1568
1743
|
provider,
|
|
1569
1744
|
until,
|
|
1570
1745
|
models: cooled.map((c) => c.model)
|
|
1571
1746
|
});
|
|
1572
1747
|
}
|
|
1748
|
+
await failOver(sessionID, provider, reason2 || "rate-limited");
|
|
1573
1749
|
}
|
|
1574
1750
|
return;
|
|
1575
1751
|
}
|
|
@@ -1589,9 +1765,12 @@ var agentPoolHooks = (async ({ client, serverUrl }, options) => {
|
|
|
1589
1765
|
if (!info || info.role !== "assistant" || !info.error) return;
|
|
1590
1766
|
const haystack = JSON.stringify(info.error) ?? "";
|
|
1591
1767
|
const status = typeof info.error.data?.statusCode === "number" ? info.error.data.statusCode : void 0;
|
|
1592
|
-
|
|
1768
|
+
const limited = isLimitText(haystack) || status === 429;
|
|
1769
|
+
const gateway5xx = status !== void 0 && status >= 500 && status <= 599;
|
|
1770
|
+
const transientGateway = gateway5xx || isTransientGatewayText(haystack);
|
|
1771
|
+
if (!limited && !transientGateway) return;
|
|
1593
1772
|
const model = `${info.providerID ?? "?"}/${info.modelID ?? "?"}`;
|
|
1594
|
-
if (isUnavailableText(haystack)) {
|
|
1773
|
+
if (isUnavailableText(haystack) || !limited && transientGateway) {
|
|
1595
1774
|
const transient = coolUnavailable(s, model);
|
|
1596
1775
|
if (transient) await log("warn", `cooling ${model} for ${Math.round((transient.until - Date.now()) / 1e3)}s (${transient.reason})`);
|
|
1597
1776
|
return;
|
|
@@ -1600,6 +1779,7 @@ var agentPoolHooks = (async ({ client, serverUrl }, options) => {
|
|
|
1600
1779
|
const reason = haystack.match(/rate[\s_-]?limit|usage limit|too many requests|quota|freelimiterror|overloaded/i)?.[0]?.toLowerCase().replace(/[\s_]/g, "-");
|
|
1601
1780
|
const strike = recordStrike(s, model, reason ?? "rate-limited", stated);
|
|
1602
1781
|
if (strike) await log("warn", `cooling ${model} for ${Math.round((strike.until - Date.now()) / 1e3)}s (${reason ?? "rate-limited"}, strike ${strike.strikes})`);
|
|
1782
|
+
await failOver(info.sessionID, model, reason ?? "rate-limited");
|
|
1603
1783
|
} catch (error) {
|
|
1604
1784
|
await log("error", `limit detection failed: ${String(error)}`);
|
|
1605
1785
|
}
|
|
@@ -1683,7 +1863,9 @@ var agentPoolHooks = (async ({ client, serverUrl }, options) => {
|
|
|
1683
1863
|
};
|
|
1684
1864
|
const total = [...snap.counts.values()].reduce((sum, n) => sum + n, 0);
|
|
1685
1865
|
const coolingNow = [...snap.cooling.entries()].filter(([, st]) => st.until > now);
|
|
1686
|
-
const
|
|
1866
|
+
const verdicts = [...snap.own.values()].map((claim) => condemnReason(s, claim, now));
|
|
1867
|
+
const hung = verdicts.filter((verdict) => verdict === "hung").length;
|
|
1868
|
+
const overran = verdicts.filter((verdict) => verdict === "overran").length;
|
|
1687
1869
|
return {
|
|
1688
1870
|
title: `agent-pool: ${total} task(s) in flight, ${coolingNow.length} model(s) cooling`,
|
|
1689
1871
|
output: [
|
|
@@ -1697,6 +1879,7 @@ round robin in slot order, each slot taking as many turns as its weight says, so
|
|
|
1697
1879
|
"",
|
|
1698
1880
|
`${total} claim(s) in flight across ${snap.siblings.length + 1} process(es).`,
|
|
1699
1881
|
`hung past ${Math.round(STUCK_MIN_AGE_MS / 6e4)}min with ${Math.round(STUCK_IDLE_MS / 6e4)}min silence: ${hung} (auto-aborted); reaped total: ${s.reaped}.`,
|
|
1882
|
+
`overran past ${Math.round(MAX_CLAIM_AGE_MS / 6e4)}min: ${overran} (auto-aborted).`,
|
|
1700
1883
|
`watchdog alive: last sweep ${s.lastSweep ? `${Math.max(0, Math.round((now - s.lastSweep) / 1e3))}s ago` : "never"} (every ${Math.round(reapMs / 1e3)}s).`,
|
|
1701
1884
|
coolingNow.length === 0 ? "no models cooling down." : `cooling: ${coolingNow.map(([m, st]) => `${m} for ${Math.ceil((st.until - now) / 1e3)}s`).join(", ")}`
|
|
1702
1885
|
].join("\n")
|
|
@@ -1713,28 +1896,10 @@ round robin in slot order, each slot taking as many turns as its weight says, so
|
|
|
1713
1896
|
});
|
|
1714
1897
|
|
|
1715
1898
|
// src/plugins/context-autoupdate.ts
|
|
1716
|
-
import { existsSync as existsSync2, readdirSync as readdirSync2, statSync as statSync2 } from "node:fs";
|
|
1899
|
+
import { existsSync as existsSync2, readdirSync as readdirSync2, statSync as statSync2, mkdirSync as mkdirSync2, writeFileSync as writeFileSync2 } from "node:fs";
|
|
1717
1900
|
import * as path2 from "node:path";
|
|
1718
1901
|
|
|
1719
|
-
// src/lib/
|
|
1720
|
-
import { readFileSync as readFileSync3 } from "node:fs";
|
|
1721
|
-
import * as path from "node:path";
|
|
1722
|
-
var CONTEXT_DIR = path.join(".opencode", "context");
|
|
1723
|
-
var WRITER = "context";
|
|
1724
|
-
var toPosix = (value) => value.split(path.sep).join("/");
|
|
1725
|
-
var compilePattern = (pattern) => {
|
|
1726
|
-
const escaped = toPosix(pattern).replace(/[.+^${}()|[\]\\]/g, "\\$&").replace(/\*/g, ".*").replace(/\?/g, ".");
|
|
1727
|
-
return new RegExp(`^${escaped}$`, "s");
|
|
1728
|
-
};
|
|
1729
|
-
var winningRule = (rule, file) => {
|
|
1730
|
-
if (typeof rule === "string") return { pattern: "<whole tool>", action: rule };
|
|
1731
|
-
if (!rule || typeof rule !== "object") return void 0;
|
|
1732
|
-
let winner;
|
|
1733
|
-
for (const [pattern, action] of Object.entries(rule)) {
|
|
1734
|
-
if (compilePattern(pattern).test(file)) winner = { pattern, action: String(action) };
|
|
1735
|
-
}
|
|
1736
|
-
return winner;
|
|
1737
|
-
};
|
|
1902
|
+
// src/lib/jsonc.ts
|
|
1738
1903
|
var stripJsonc = (raw) => {
|
|
1739
1904
|
let out = "";
|
|
1740
1905
|
let i = 0;
|
|
@@ -1773,9 +1938,37 @@ var stripJsonc = (raw) => {
|
|
|
1773
1938
|
}
|
|
1774
1939
|
return out.replace(/,(\s*[}\]])/g, "$1");
|
|
1775
1940
|
};
|
|
1941
|
+
var parseJsonc = (raw) => {
|
|
1942
|
+
try {
|
|
1943
|
+
return JSON.parse(stripJsonc(raw));
|
|
1944
|
+
} catch {
|
|
1945
|
+
return void 0;
|
|
1946
|
+
}
|
|
1947
|
+
};
|
|
1948
|
+
|
|
1949
|
+
// src/lib/writer-rule.ts
|
|
1950
|
+
import { readFileSync as readFileSync3 } from "node:fs";
|
|
1951
|
+
import * as path from "node:path";
|
|
1952
|
+
var CONTEXT_DIR = path.join(".opencode", "context");
|
|
1953
|
+
var WRITER = "context";
|
|
1954
|
+
var MANAGER_WRITER = MANAGER_AGENT_NAME;
|
|
1955
|
+
var toPosix = (value) => value.split(path.sep).join("/");
|
|
1956
|
+
var compilePattern = (pattern) => {
|
|
1957
|
+
const escaped = toPosix(pattern).replace(/[.+^${}()|[\]\\]/g, "\\$&").replace(/\*/g, ".*").replace(/\?/g, ".");
|
|
1958
|
+
return new RegExp(`^${escaped}$`, "s");
|
|
1959
|
+
};
|
|
1960
|
+
var winningRule = (rule, file) => {
|
|
1961
|
+
if (typeof rule === "string") return { pattern: "<whole tool>", action: rule };
|
|
1962
|
+
if (!rule || typeof rule !== "object") return void 0;
|
|
1963
|
+
let winner;
|
|
1964
|
+
for (const [pattern, action] of Object.entries(rule)) {
|
|
1965
|
+
if (compilePattern(pattern).test(file)) winner = { pattern, action: String(action) };
|
|
1966
|
+
}
|
|
1967
|
+
return winner;
|
|
1968
|
+
};
|
|
1776
1969
|
var readAgents = (raw) => {
|
|
1777
1970
|
try {
|
|
1778
|
-
const agents =
|
|
1971
|
+
const agents = parseJsonc(raw).agent;
|
|
1779
1972
|
if (agents && typeof agents === "object" && !Array.isArray(agents)) return agents;
|
|
1780
1973
|
return {};
|
|
1781
1974
|
} catch {
|
|
@@ -1787,6 +1980,10 @@ var evaluatedForm = (mapDir, projectRoot) => {
|
|
|
1787
1980
|
const absolute = toPosix(path.join(mapDir, "architecture.md"));
|
|
1788
1981
|
return projectRoot ? toPosix(path.relative(projectRoot, absolute)) : absolute;
|
|
1789
1982
|
};
|
|
1983
|
+
var managerForm = (mapDir, projectRoot) => {
|
|
1984
|
+
const absolute = toPosix(path.join(mapDir, "manager.md"));
|
|
1985
|
+
return projectRoot ? toPosix(path.relative(projectRoot, absolute)) : absolute;
|
|
1986
|
+
};
|
|
1790
1987
|
var verifyWriterRule = (configPath, mapDir, projectRoot) => {
|
|
1791
1988
|
let raw;
|
|
1792
1989
|
try {
|
|
@@ -1818,6 +2015,19 @@ var verifyWriterRule = (configPath, mapDir, projectRoot) => {
|
|
|
1818
2015
|
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.`;
|
|
1819
2016
|
}
|
|
1820
2017
|
}
|
|
2018
|
+
const managerPath = managerForm(mapDir, projectRoot);
|
|
2019
|
+
const agentDefs = readAgents(raw);
|
|
2020
|
+
if (agentDefs) {
|
|
2021
|
+
const writers = [];
|
|
2022
|
+
for (const [name, def] of Object.entries(agentDefs)) {
|
|
2023
|
+
if (name === MANAGER_WRITER) continue;
|
|
2024
|
+
const win = winningRule(permissionOf(def), managerPath);
|
|
2025
|
+
if (win?.action === "allow") writers.push(`${name} (edit rule "${win.pattern}": "${win.action}")`);
|
|
2026
|
+
}
|
|
2027
|
+
if (writers.length > 0) {
|
|
2028
|
+
return `${CONTEXT_DIR}/manager.md is writable by permission: ${writers.join(", ")}. It is the client's own file \u2014 what they are working on, what is ratified, what every agent must respect \u2014 and only the \`${MANAGER_WRITER}\` agent (run solely by /manager-directive) may write it. Deny it LAST on every other agent, after any broad allow, because opencode takes the LAST matching rule: "*": "allow", "*/${CONTEXT_DIR}/manager.md": "deny", "${CONTEXT_DIR}/manager.md": "deny". Declare this on every agent above, including \`${WRITER}\`: an agent you define yourself replaces the shipped one wholesale, permission and all.`;
|
|
2029
|
+
}
|
|
2030
|
+
}
|
|
1821
2031
|
return void 0;
|
|
1822
2032
|
};
|
|
1823
2033
|
|
|
@@ -1844,6 +2054,51 @@ var IGNORED_SEGMENTS = /* @__PURE__ */ new Set([
|
|
|
1844
2054
|
".cache",
|
|
1845
2055
|
".git"
|
|
1846
2056
|
]);
|
|
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
|
+
};
|
|
1847
2102
|
var toPosix2 = (value) => value.split(path2.sep).join("/");
|
|
1848
2103
|
var findProjectConfig = () => {
|
|
1849
2104
|
let dir = import.meta.dir ?? path2.dirname(new URL(import.meta.url).pathname);
|
|
@@ -1887,6 +2142,7 @@ var contextAutoUpdateHooks = (async ({ client, directory }, options) => {
|
|
|
1887
2142
|
});
|
|
1888
2143
|
const root = path2.resolve(directory);
|
|
1889
2144
|
const contextAbs = path2.join(root, CONTEXT_DIR2);
|
|
2145
|
+
if (ensureManagerFile(root).created) void log("info", `created ${CONTEXT_DIR2}/manager.md`);
|
|
1890
2146
|
const config = findProjectConfig();
|
|
1891
2147
|
const writerProblem = config ? verifyWriterRule(config, contextAbs, root) : void 0;
|
|
1892
2148
|
if (writerProblem) void log("error", writerProblem);
|
|
@@ -1946,7 +2202,7 @@ var contextAutoUpdateHooks = (async ({ client, directory }, options) => {
|
|
|
1946
2202
|
const requeue = () => {
|
|
1947
2203
|
for (const file of files) pending.add(file);
|
|
1948
2204
|
};
|
|
1949
|
-
const mode = existsSync2(contextAbs) ? "update" : "bootstrap";
|
|
2205
|
+
const mode = existsSync2(path2.join(contextAbs, "contexts.md")) ? "update" : "bootstrap";
|
|
1950
2206
|
init(state2, (m) => void log("warn", m));
|
|
1951
2207
|
const snap = snapshot(state2);
|
|
1952
2208
|
const slot = pickSlot(state2, snap);
|
|
@@ -2846,7 +3102,7 @@ var omniRouteHooks = async (input, options, deps = {}) => {
|
|
|
2846
3102
|
|
|
2847
3103
|
// src/lib/selfupdate.ts
|
|
2848
3104
|
import { spawn as spawn2 } from "node:child_process";
|
|
2849
|
-
import { closeSync, mkdirSync as
|
|
3105
|
+
import { closeSync, mkdirSync as mkdirSync3, openSync, readFileSync as readFileSync4, renameSync as renameSync2, rmSync, statSync as statSync3, unlinkSync as unlinkSync2, writeFileSync as writeFileSync3, writeSync } from "node:fs";
|
|
2850
3106
|
import { homedir as homedir2 } from "node:os";
|
|
2851
3107
|
import { dirname as dirname3, join as join6, resolve as resolve2 } from "node:path";
|
|
2852
3108
|
var REGISTRY_LATEST = `https://registry.npmjs.org/${PACKAGE_NAME}/latest`;
|
|
@@ -2871,9 +3127,17 @@ function compareSemver(a, b) {
|
|
|
2871
3127
|
}
|
|
2872
3128
|
return 0;
|
|
2873
3129
|
}
|
|
2874
|
-
function readInstalledVersion(packageRoot) {
|
|
3130
|
+
function readInstalledVersion(packageRoot, read = (path4) => {
|
|
3131
|
+
try {
|
|
3132
|
+
return readFileSync4(path4, "utf8");
|
|
3133
|
+
} catch {
|
|
3134
|
+
return void 0;
|
|
3135
|
+
}
|
|
3136
|
+
}) {
|
|
2875
3137
|
try {
|
|
2876
|
-
const
|
|
3138
|
+
const raw = read(join6(packageRoot, "package.json"));
|
|
3139
|
+
if (raw === void 0) return void 0;
|
|
3140
|
+
const manifest = JSON.parse(raw);
|
|
2877
3141
|
if (manifest.name !== PACKAGE_NAME) return void 0;
|
|
2878
3142
|
return typeof manifest.version === "string" ? manifest.version : void 0;
|
|
2879
3143
|
} catch {
|
|
@@ -2932,8 +3196,8 @@ function readState(path4 = STATE_PATH) {
|
|
|
2932
3196
|
}
|
|
2933
3197
|
function writeState(state2, path4 = STATE_PATH) {
|
|
2934
3198
|
try {
|
|
2935
|
-
|
|
2936
|
-
|
|
3199
|
+
mkdirSync3(dirname3(path4), { recursive: true });
|
|
3200
|
+
writeFileSync3(path4, JSON.stringify(state2));
|
|
2937
3201
|
} catch {
|
|
2938
3202
|
}
|
|
2939
3203
|
}
|
|
@@ -2959,7 +3223,7 @@ function acquireLock(path4 = LOCK_PATH, now = Date.now()) {
|
|
|
2959
3223
|
};
|
|
2960
3224
|
};
|
|
2961
3225
|
try {
|
|
2962
|
-
|
|
3226
|
+
mkdirSync3(dirname3(path4), { recursive: true });
|
|
2963
3227
|
return claim();
|
|
2964
3228
|
} catch {
|
|
2965
3229
|
let orphaned = false;
|
|
@@ -3148,7 +3412,7 @@ var LaCode = (async (input, options) => {
|
|
|
3148
3412
|
await log("info", `commands: added ${cmds.added.join(", ")}; kept ${cmds.kept.join(", ") || "none"} already defined`);
|
|
3149
3413
|
}
|
|
3150
3414
|
if (applyInstructions(cfg).added) {
|
|
3151
|
-
await log("info", "instructions:
|
|
3415
|
+
await log("info", "instructions: ensured the standing context-map rule is present");
|
|
3152
3416
|
}
|
|
3153
3417
|
await omniroute.config?.(cfg);
|
|
3154
3418
|
await pool.config?.(cfg);
|