@dylanrussell/agent-router 1.0.6 → 1.1.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/README.md +82 -9
- package/dist/cli.js +421 -25
- package/dist/cli.js.map +1 -1
- package/dist/plugin.js +609 -25
- package/dist/plugin.js.map +1 -1
- package/dist/tui.js +411 -16
- package/dist/tui.js.map +1 -1
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -15244,19 +15244,31 @@ var init_zod = __esm({
|
|
|
15244
15244
|
});
|
|
15245
15245
|
|
|
15246
15246
|
// src/core/schema.ts
|
|
15247
|
-
var StateFileSchema, AgentEntrySchema, StackFileSchema, ConfigFileSchema, OpencodeJsonSchema;
|
|
15247
|
+
var FallbackSchema, RoutingEntrySchema, StateFileSchema, AgentEntrySchema, StackFileSchema, ConfigFileSchema, OpencodeJsonSchema;
|
|
15248
15248
|
var init_schema = __esm({
|
|
15249
15249
|
"src/core/schema.ts"() {
|
|
15250
15250
|
"use strict";
|
|
15251
15251
|
init_zod();
|
|
15252
|
+
FallbackSchema = external_exports.object({
|
|
15253
|
+
model: external_exports.string().regex(/^[^/\s]+\/\S+$/, "Expected provider/model"),
|
|
15254
|
+
variant: external_exports.string().min(1).optional()
|
|
15255
|
+
}).strict();
|
|
15256
|
+
RoutingEntrySchema = external_exports.object({
|
|
15257
|
+
model: external_exports.string().min(1),
|
|
15258
|
+
variant: external_exports.string().min(1).nullable().optional(),
|
|
15259
|
+
fallbacks: external_exports.array(FallbackSchema).max(8).optional()
|
|
15260
|
+
}).strict();
|
|
15252
15261
|
StateFileSchema = external_exports.object({
|
|
15253
15262
|
version: external_exports.literal(1),
|
|
15254
15263
|
active: external_exports.string().min(1),
|
|
15255
15264
|
previousActive: external_exports.string().min(1).nullable(),
|
|
15256
|
-
lastSwitchedAt: external_exports.string().min(1)
|
|
15265
|
+
lastSwitchedAt: external_exports.string().min(1),
|
|
15266
|
+
fallbackAgents: external_exports.record(external_exports.string(), RoutingEntrySchema).optional()
|
|
15257
15267
|
}).strict();
|
|
15258
15268
|
AgentEntrySchema = external_exports.object({
|
|
15259
|
-
model: external_exports.string().min(1)
|
|
15269
|
+
model: external_exports.string().min(1),
|
|
15270
|
+
variant: external_exports.string().min(1).nullable().optional(),
|
|
15271
|
+
fallbacks: external_exports.array(FallbackSchema).max(8).optional()
|
|
15260
15272
|
}).passthrough();
|
|
15261
15273
|
StackFileSchema = external_exports.object({
|
|
15262
15274
|
agents: external_exports.record(external_exports.string(), AgentEntrySchema)
|
|
@@ -16086,8 +16098,300 @@ init_errors();
|
|
|
16086
16098
|
import { existsSync as existsSync3 } from "fs";
|
|
16087
16099
|
import { readFile as readFile2, readdir } from "fs/promises";
|
|
16088
16100
|
import path4 from "path";
|
|
16101
|
+
|
|
16102
|
+
// src/core/yaml-lite.ts
|
|
16103
|
+
var RESERVED_WORDS = /* @__PURE__ */ new Set([
|
|
16104
|
+
"null",
|
|
16105
|
+
"true",
|
|
16106
|
+
"false",
|
|
16107
|
+
"yes",
|
|
16108
|
+
"no",
|
|
16109
|
+
"on",
|
|
16110
|
+
"off",
|
|
16111
|
+
"~",
|
|
16112
|
+
// YAML 1.1 nulls
|
|
16113
|
+
"Null",
|
|
16114
|
+
"NULL",
|
|
16115
|
+
"True",
|
|
16116
|
+
"False",
|
|
16117
|
+
"Yes",
|
|
16118
|
+
"No",
|
|
16119
|
+
"On",
|
|
16120
|
+
"Off"
|
|
16121
|
+
]);
|
|
16122
|
+
var BARE_STRING_RE = /^[A-Za-z0-9._\-\/+@$%^&()~]+$/;
|
|
16123
|
+
function needsQuoting(s) {
|
|
16124
|
+
if (s === "") return true;
|
|
16125
|
+
if (RESERVED_WORDS.has(s)) return true;
|
|
16126
|
+
if (/^[+-]?(\d+\.?\d*|\.\d+)([eE][+-]?\d+)?$/.test(s)) return true;
|
|
16127
|
+
if (/^0x[0-9A-Fa-f]+$/.test(s)) return true;
|
|
16128
|
+
if (/^[+-]?\.(inf|Inf|INF|nan|NaN|NAN)$/.test(s)) return true;
|
|
16129
|
+
if (!BARE_STRING_RE.test(s)) return true;
|
|
16130
|
+
return false;
|
|
16131
|
+
}
|
|
16132
|
+
function serializeOptionValue(value) {
|
|
16133
|
+
if (value === null || value === void 0) return "null";
|
|
16134
|
+
if (typeof value === "boolean") return value ? "true" : "false";
|
|
16135
|
+
if (typeof value === "number") {
|
|
16136
|
+
if (!Number.isFinite(value)) return String(value);
|
|
16137
|
+
return JSON.stringify(value);
|
|
16138
|
+
}
|
|
16139
|
+
if (typeof value === "bigint") return String(value);
|
|
16140
|
+
if (typeof value === "string") return needsQuoting(value) ? JSON.stringify(value) : value;
|
|
16141
|
+
return JSON.stringify(value);
|
|
16142
|
+
}
|
|
16143
|
+
function stripComment(line) {
|
|
16144
|
+
let inSingle = false;
|
|
16145
|
+
let inDouble = false;
|
|
16146
|
+
for (let i = 0; i < line.length; i++) {
|
|
16147
|
+
const ch = line[i];
|
|
16148
|
+
if (ch === "'" && !inDouble) inSingle = !inSingle;
|
|
16149
|
+
else if (ch === '"' && !inSingle) inDouble = !inDouble;
|
|
16150
|
+
else if (ch === "#" && !inSingle && !inDouble) {
|
|
16151
|
+
if (i === 0 || /\s/.test(line[i - 1] ?? "")) return line.slice(0, i);
|
|
16152
|
+
}
|
|
16153
|
+
}
|
|
16154
|
+
return line;
|
|
16155
|
+
}
|
|
16156
|
+
function tokenize(block) {
|
|
16157
|
+
const out = [];
|
|
16158
|
+
for (const raw of block.split(/\r?\n/)) {
|
|
16159
|
+
if (raw.trim() === "") continue;
|
|
16160
|
+
const indentMatch = /^( *)/.exec(raw);
|
|
16161
|
+
const indent = indentMatch?.[1]?.length ?? 0;
|
|
16162
|
+
const content = stripComment(raw.slice(indent));
|
|
16163
|
+
if (content.trim() === "") continue;
|
|
16164
|
+
out.push({ indent, text: content.trimEnd(), raw });
|
|
16165
|
+
}
|
|
16166
|
+
return out;
|
|
16167
|
+
}
|
|
16168
|
+
function coerceScalar(raw) {
|
|
16169
|
+
const v = raw.trim();
|
|
16170
|
+
if (v === "" || v === "null" || v === "~" || v === "Null" || v === "NULL") return null;
|
|
16171
|
+
if (v === "true" || v === "True" || v === "TRUE") return true;
|
|
16172
|
+
if (v === "false" || v === "False" || v === "FALSE") return false;
|
|
16173
|
+
if (v === "yes" || v === "Yes" || v === "YES") return true;
|
|
16174
|
+
if (v === "no" || v === "No" || v === "NO") return false;
|
|
16175
|
+
if (v === "on" || v === "On" || v === "ON") return true;
|
|
16176
|
+
if (v === "off" || v === "Off" || v === "OFF") return false;
|
|
16177
|
+
if (/^[+-]?\d+$/.test(v)) return Number.parseInt(v, 10);
|
|
16178
|
+
if (/^[+-]?(\d+\.?\d*|\.\d+)([eE][+-]?\d+)?$/.test(v)) return Number.parseFloat(v);
|
|
16179
|
+
if (/^0x[0-9A-Fa-f]+$/.test(v)) return Number.parseInt(v, 16);
|
|
16180
|
+
return v;
|
|
16181
|
+
}
|
|
16182
|
+
function parseQuoted(s) {
|
|
16183
|
+
const quote = s[0] ?? "";
|
|
16184
|
+
if (quote !== '"' && quote !== "'") return null;
|
|
16185
|
+
if (quote === '"') {
|
|
16186
|
+
let out2 = "";
|
|
16187
|
+
for (let i = 1; i < s.length; i++) {
|
|
16188
|
+
const ch = s[i] ?? "";
|
|
16189
|
+
if (ch === "\\" && i + 1 < s.length) {
|
|
16190
|
+
const next = s[i + 1] ?? "";
|
|
16191
|
+
const map2 = {
|
|
16192
|
+
n: "\n",
|
|
16193
|
+
t: " ",
|
|
16194
|
+
r: "\r",
|
|
16195
|
+
'"': '"',
|
|
16196
|
+
"\\": "\\",
|
|
16197
|
+
"/": "/",
|
|
16198
|
+
b: "\b",
|
|
16199
|
+
f: "\f"
|
|
16200
|
+
};
|
|
16201
|
+
out2 += map2[next] ?? next;
|
|
16202
|
+
i++;
|
|
16203
|
+
} else if (ch === '"') {
|
|
16204
|
+
return { value: out2, rest: s.slice(i + 1) };
|
|
16205
|
+
} else {
|
|
16206
|
+
out2 += ch;
|
|
16207
|
+
}
|
|
16208
|
+
}
|
|
16209
|
+
return null;
|
|
16210
|
+
}
|
|
16211
|
+
let out = "";
|
|
16212
|
+
for (let i = 1; i < s.length; i++) {
|
|
16213
|
+
const ch = s[i] ?? "";
|
|
16214
|
+
if (ch === "'") {
|
|
16215
|
+
if (s[i + 1] === "'") {
|
|
16216
|
+
out += "'";
|
|
16217
|
+
i++;
|
|
16218
|
+
} else {
|
|
16219
|
+
return { value: out, rest: s.slice(i + 1) };
|
|
16220
|
+
}
|
|
16221
|
+
} else {
|
|
16222
|
+
out += ch;
|
|
16223
|
+
}
|
|
16224
|
+
}
|
|
16225
|
+
return null;
|
|
16226
|
+
}
|
|
16227
|
+
function splitFlow(raw) {
|
|
16228
|
+
const parts = [];
|
|
16229
|
+
let depth = 0;
|
|
16230
|
+
let inSingle = false;
|
|
16231
|
+
let inDouble = false;
|
|
16232
|
+
let start = 0;
|
|
16233
|
+
for (let i = 0; i < raw.length; i++) {
|
|
16234
|
+
const ch = raw[i];
|
|
16235
|
+
if (ch === "'" && !inDouble) inSingle = !inSingle;
|
|
16236
|
+
else if (ch === '"' && !inSingle) inDouble = !inDouble;
|
|
16237
|
+
else if (!inSingle && !inDouble) {
|
|
16238
|
+
if (ch === "{" || ch === "[") depth++;
|
|
16239
|
+
else if (ch === "}" || ch === "]") depth--;
|
|
16240
|
+
else if (ch === "," && depth === 0) {
|
|
16241
|
+
parts.push(raw.slice(start, i));
|
|
16242
|
+
start = i + 1;
|
|
16243
|
+
}
|
|
16244
|
+
}
|
|
16245
|
+
}
|
|
16246
|
+
parts.push(raw.slice(start));
|
|
16247
|
+
return parts;
|
|
16248
|
+
}
|
|
16249
|
+
function parseFlowValue(raw) {
|
|
16250
|
+
const s = raw.trim();
|
|
16251
|
+
if (s === "") return null;
|
|
16252
|
+
const first = s[0] ?? "";
|
|
16253
|
+
if (first === "{") {
|
|
16254
|
+
const inner = s.slice(1, s.length - 1);
|
|
16255
|
+
const obj = {};
|
|
16256
|
+
for (const part of splitFlow(inner)) {
|
|
16257
|
+
const t = part.trim();
|
|
16258
|
+
if (t === "") continue;
|
|
16259
|
+
const colon = findColon(t);
|
|
16260
|
+
if (colon < 0) {
|
|
16261
|
+
obj[t] = null;
|
|
16262
|
+
} else {
|
|
16263
|
+
const k = t.slice(0, colon).trim();
|
|
16264
|
+
const v = t.slice(colon + 1).trim();
|
|
16265
|
+
obj[stripKeyQuotes(k)] = parseFlowValue(v);
|
|
16266
|
+
}
|
|
16267
|
+
}
|
|
16268
|
+
return obj;
|
|
16269
|
+
}
|
|
16270
|
+
if (first === "[") {
|
|
16271
|
+
const inner = s.slice(1, s.length - 1);
|
|
16272
|
+
return splitFlow(inner).filter((p) => p.trim() !== "").map((p) => parseFlowValue(p));
|
|
16273
|
+
}
|
|
16274
|
+
if (first === '"' || first === "'") {
|
|
16275
|
+
const q = parseQuoted(s);
|
|
16276
|
+
return q ? q.value : s;
|
|
16277
|
+
}
|
|
16278
|
+
return coerceScalar(s);
|
|
16279
|
+
}
|
|
16280
|
+
function findColon(s) {
|
|
16281
|
+
let inSingle = false;
|
|
16282
|
+
let inDouble = false;
|
|
16283
|
+
let depth = 0;
|
|
16284
|
+
for (let i = 0; i < s.length; i++) {
|
|
16285
|
+
const ch = s[i];
|
|
16286
|
+
if (ch === "'" && !inDouble) inSingle = !inSingle;
|
|
16287
|
+
else if (ch === '"' && !inSingle) inDouble = !inDouble;
|
|
16288
|
+
else if (!inSingle && !inDouble) {
|
|
16289
|
+
if (ch === "{" || ch === "[") depth++;
|
|
16290
|
+
else if (ch === "}" || ch === "]") depth--;
|
|
16291
|
+
else if (ch === ":" && depth === 0) return i;
|
|
16292
|
+
}
|
|
16293
|
+
}
|
|
16294
|
+
return -1;
|
|
16295
|
+
}
|
|
16296
|
+
function stripKeyQuotes(k) {
|
|
16297
|
+
if (k.length >= 2 && (k[0] === '"' && k.at(-1) === '"' || k[0] === "'" && k.at(-1) === "'")) {
|
|
16298
|
+
return k.slice(1, -1);
|
|
16299
|
+
}
|
|
16300
|
+
return k;
|
|
16301
|
+
}
|
|
16302
|
+
function parseMapping(lines, startIdx, indent) {
|
|
16303
|
+
const out = {};
|
|
16304
|
+
let i = startIdx;
|
|
16305
|
+
while (i < lines.length) {
|
|
16306
|
+
const line = lines[i];
|
|
16307
|
+
if (!line) break;
|
|
16308
|
+
if (line.indent < indent) break;
|
|
16309
|
+
if (line.indent > indent) {
|
|
16310
|
+
i++;
|
|
16311
|
+
continue;
|
|
16312
|
+
}
|
|
16313
|
+
const colon = findColon(line.text);
|
|
16314
|
+
if (colon < 0) {
|
|
16315
|
+
i++;
|
|
16316
|
+
continue;
|
|
16317
|
+
}
|
|
16318
|
+
const key = stripKeyQuotes(line.text.slice(0, colon).trim());
|
|
16319
|
+
if (key === "") {
|
|
16320
|
+
i++;
|
|
16321
|
+
continue;
|
|
16322
|
+
}
|
|
16323
|
+
const rest = line.text.slice(colon + 1).trim();
|
|
16324
|
+
if (rest !== "") {
|
|
16325
|
+
out[key] = parseFlowValue(rest);
|
|
16326
|
+
i++;
|
|
16327
|
+
} else {
|
|
16328
|
+
const childIndent = lines[i + 1]?.indent ?? line.indent + 1;
|
|
16329
|
+
let j = i + 1;
|
|
16330
|
+
while (j < lines.length && (lines[j]?.indent ?? -1) >= childIndent) j++;
|
|
16331
|
+
if (j === i + 1) {
|
|
16332
|
+
out[key] = null;
|
|
16333
|
+
i++;
|
|
16334
|
+
} else {
|
|
16335
|
+
const childBlock = lines.slice(i + 1, j);
|
|
16336
|
+
const firstChild = childBlock[0];
|
|
16337
|
+
if (firstChild?.text.trimStart().startsWith("- ")) {
|
|
16338
|
+
out[key] = parseSequence(childBlock, childIndent);
|
|
16339
|
+
} else {
|
|
16340
|
+
out[key] = parseMapping(childBlock, 0, childIndent).value;
|
|
16341
|
+
}
|
|
16342
|
+
i = j;
|
|
16343
|
+
}
|
|
16344
|
+
}
|
|
16345
|
+
}
|
|
16346
|
+
return { value: out, next: i };
|
|
16347
|
+
}
|
|
16348
|
+
function parseSequence(lines, indent) {
|
|
16349
|
+
const out = [];
|
|
16350
|
+
let i = 0;
|
|
16351
|
+
while (i < lines.length) {
|
|
16352
|
+
const line = lines[i];
|
|
16353
|
+
if (!line) break;
|
|
16354
|
+
if (line.indent < indent) break;
|
|
16355
|
+
const t = line.text.trimStart();
|
|
16356
|
+
if (!t.startsWith("- ")) {
|
|
16357
|
+
i++;
|
|
16358
|
+
continue;
|
|
16359
|
+
}
|
|
16360
|
+
const item = t.slice(2).trim();
|
|
16361
|
+
if (item === "") {
|
|
16362
|
+
const childIndent = lines[i + 1]?.indent ?? line.indent + 1;
|
|
16363
|
+
let j = i + 1;
|
|
16364
|
+
while (j < lines.length && (lines[j]?.indent ?? -1) >= childIndent) j++;
|
|
16365
|
+
out.push(parseMapping(lines.slice(i + 1, j), 0, childIndent).value);
|
|
16366
|
+
i = j;
|
|
16367
|
+
} else {
|
|
16368
|
+
out.push(parseFlowValue(item));
|
|
16369
|
+
i++;
|
|
16370
|
+
}
|
|
16371
|
+
}
|
|
16372
|
+
return out;
|
|
16373
|
+
}
|
|
16374
|
+
function parseFrontmatterBlock(block) {
|
|
16375
|
+
const lines = tokenize(block);
|
|
16376
|
+
if (lines.length === 0) return null;
|
|
16377
|
+
const { value } = parseMapping(lines, 0, 0);
|
|
16378
|
+
return value;
|
|
16379
|
+
}
|
|
16380
|
+
|
|
16381
|
+
// src/core/frontmatter.ts
|
|
16089
16382
|
var FRONTMATTER_RE = /^---\r?\n([\s\S]*?)\r?\n---(?:\r?\n|$)/;
|
|
16090
16383
|
var MODEL_LINE_RE = /^model:[ \t]*(.*)$/m;
|
|
16384
|
+
var RESERVED_AGENT_KEYS = /* @__PURE__ */ new Set([
|
|
16385
|
+
"name",
|
|
16386
|
+
"mode",
|
|
16387
|
+
"description",
|
|
16388
|
+
"permission",
|
|
16389
|
+
"color",
|
|
16390
|
+
"tools",
|
|
16391
|
+
"prompt",
|
|
16392
|
+
"steps",
|
|
16393
|
+
"maxSteps"
|
|
16394
|
+
]);
|
|
16091
16395
|
function cleanModelValue(raw) {
|
|
16092
16396
|
let v = raw;
|
|
16093
16397
|
const hash2 = v.search(/[ \t]#/);
|
|
@@ -16116,6 +16420,55 @@ function setFrontmatterModel(content, model) {
|
|
|
16116
16420
|
const nextFm = fm[0].slice(0, blockStart) + nextBlock + fm[0].slice(blockStart + block.length);
|
|
16117
16421
|
return nextFm + content.slice(fm[0].length);
|
|
16118
16422
|
}
|
|
16423
|
+
function getFrontmatterOptions(content) {
|
|
16424
|
+
const fm = FRONTMATTER_RE.exec(content);
|
|
16425
|
+
if (!fm?.[1]) return {};
|
|
16426
|
+
const parsed = parseFrontmatterBlock(fm[1]);
|
|
16427
|
+
if (!parsed) return {};
|
|
16428
|
+
const out = {};
|
|
16429
|
+
for (const [k, v] of Object.entries(parsed)) {
|
|
16430
|
+
if (k === "model" || RESERVED_AGENT_KEYS.has(k)) continue;
|
|
16431
|
+
out[k] = v;
|
|
16432
|
+
}
|
|
16433
|
+
return out;
|
|
16434
|
+
}
|
|
16435
|
+
function setFrontmatterOptions(content, options) {
|
|
16436
|
+
const fm = FRONTMATTER_RE.exec(content);
|
|
16437
|
+
if (!fm?.[1]) throw new Error("no frontmatter block");
|
|
16438
|
+
const block = fm[1];
|
|
16439
|
+
const keys = Object.keys(options);
|
|
16440
|
+
if (keys.length === 0) return content;
|
|
16441
|
+
const eol = block.includes("\r\n") ? "\r\n" : "\n";
|
|
16442
|
+
const lines = block.split(/\r?\n/);
|
|
16443
|
+
const handled = /* @__PURE__ */ new Set();
|
|
16444
|
+
for (let i = 0; i < lines.length; i++) {
|
|
16445
|
+
const m = /^([A-Za-z0-9_-]+):[ \t]*(.*)$/.exec(lines[i] ?? "");
|
|
16446
|
+
if (!m) continue;
|
|
16447
|
+
const key = m[1] ?? "";
|
|
16448
|
+
if (!(key in options)) continue;
|
|
16449
|
+
handled.add(key);
|
|
16450
|
+
let end = i + 1;
|
|
16451
|
+
while (end < lines.length && /^[ \t]+/.test(lines[end] ?? "")) end++;
|
|
16452
|
+
const value = options[key];
|
|
16453
|
+
if (value === null || value === void 0) {
|
|
16454
|
+
lines.splice(i, end - i);
|
|
16455
|
+
i--;
|
|
16456
|
+
} else {
|
|
16457
|
+
lines.splice(i, end - i, `${key}: ${serializeOptionValue(value)}`);
|
|
16458
|
+
}
|
|
16459
|
+
}
|
|
16460
|
+
const appended = [];
|
|
16461
|
+
for (const [key, value] of Object.entries(options)) {
|
|
16462
|
+
if (handled.has(key)) continue;
|
|
16463
|
+
if (value === null || value === void 0) continue;
|
|
16464
|
+
appended.push(`${key}: ${serializeOptionValue(value)}`);
|
|
16465
|
+
}
|
|
16466
|
+
const nextBlock = appended.length > 0 ? lines.join(eol) + eol + appended.join(eol) : lines.join(eol);
|
|
16467
|
+
if (nextBlock === block) return content;
|
|
16468
|
+
const blockStart = fm[0].indexOf(block);
|
|
16469
|
+
const nextFm = fm[0].slice(0, blockStart) + nextBlock + fm[0].slice(blockStart + block.length);
|
|
16470
|
+
return nextFm + content.slice(fm[0].length);
|
|
16471
|
+
}
|
|
16119
16472
|
function agentFilePath(agentsDir, name) {
|
|
16120
16473
|
return path4.join(agentsDir, `${name}.md`);
|
|
16121
16474
|
}
|
|
@@ -16129,7 +16482,7 @@ async function listAgentFiles(agentsDir) {
|
|
|
16129
16482
|
}
|
|
16130
16483
|
return names.filter((n) => n.endsWith(".md")).map((n) => n.slice(0, -".md".length)).sort();
|
|
16131
16484
|
}
|
|
16132
|
-
async function
|
|
16485
|
+
async function readAgentEntries(agentsDir) {
|
|
16133
16486
|
const out = {};
|
|
16134
16487
|
for (const name of await listAgentFiles(agentsDir)) {
|
|
16135
16488
|
const filePath = agentFilePath(agentsDir, name);
|
|
@@ -16140,10 +16493,17 @@ async function readAgentModels(agentsDir) {
|
|
|
16140
16493
|
throw new IOError(`Failed to read ${filePath}: ${cause.message}`, cause);
|
|
16141
16494
|
}
|
|
16142
16495
|
const model = getFrontmatterModel(content);
|
|
16143
|
-
if (model
|
|
16496
|
+
if (model === null) continue;
|
|
16497
|
+
out[name] = { model, options: getFrontmatterOptions(content) };
|
|
16144
16498
|
}
|
|
16145
16499
|
return out;
|
|
16146
16500
|
}
|
|
16501
|
+
async function readAgentModels(agentsDir) {
|
|
16502
|
+
const entries = await readAgentEntries(agentsDir);
|
|
16503
|
+
const out = {};
|
|
16504
|
+
for (const [name, entry] of Object.entries(entries)) out[name] = entry.model;
|
|
16505
|
+
return out;
|
|
16506
|
+
}
|
|
16147
16507
|
async function readAgentFileStrict(agentsDir, name) {
|
|
16148
16508
|
const filePath = agentFilePath(agentsDir, name);
|
|
16149
16509
|
if (!existsSync3(filePath)) {
|
|
@@ -16159,7 +16519,7 @@ async function readAgentFileStrict(agentsDir, name) {
|
|
|
16159
16519
|
if (model === null) {
|
|
16160
16520
|
throw new AgentFileError(name, filePath, "no frontmatter `model:` line to rewrite");
|
|
16161
16521
|
}
|
|
16162
|
-
return { filePath, content, model };
|
|
16522
|
+
return { filePath, content, model, options: getFrontmatterOptions(content) };
|
|
16163
16523
|
}
|
|
16164
16524
|
|
|
16165
16525
|
// src/core/history.ts
|
|
@@ -16349,6 +16709,9 @@ function collectModelRefs(stack) {
|
|
|
16349
16709
|
const refs = [];
|
|
16350
16710
|
for (const [k, v] of Object.entries(stack.agents)) {
|
|
16351
16711
|
refs.push({ path: `agents.${k}.model`, modelId: v.model });
|
|
16712
|
+
for (const [index, candidate] of (v.fallbacks ?? []).entries()) {
|
|
16713
|
+
refs.push({ path: `agents.${k}.fallbacks.${index}.model`, modelId: candidate.model });
|
|
16714
|
+
}
|
|
16352
16715
|
}
|
|
16353
16716
|
return refs;
|
|
16354
16717
|
}
|
|
@@ -16428,15 +16791,14 @@ async function applyStack(paths, name, options = {}) {
|
|
|
16428
16791
|
const pending = [];
|
|
16429
16792
|
for (const [agent, entry] of Object.entries(target.agents)) {
|
|
16430
16793
|
const { filePath, content, model } = await readAgentFileStrict(paths.agentsDir, agent);
|
|
16431
|
-
|
|
16432
|
-
|
|
16433
|
-
|
|
16434
|
-
|
|
16435
|
-
});
|
|
16794
|
+
const wantOptions = entryOptions(entry);
|
|
16795
|
+
const afterOptions = setFrontmatterOptions(content, wantOptions);
|
|
16796
|
+
const nextContent = entry.model === model ? afterOptions : setFrontmatterModel(afterOptions, entry.model);
|
|
16797
|
+
pending.push({ agent, filePath, next: nextContent === content ? null : nextContent });
|
|
16436
16798
|
}
|
|
16437
16799
|
const prevState = await readState(paths.statePath);
|
|
16438
16800
|
const prevActive = prevState?.active ?? null;
|
|
16439
|
-
const displaced = { agents:
|
|
16801
|
+
const displaced = { agents: await captureAgents(paths) };
|
|
16440
16802
|
const historyId = await appendHistory(
|
|
16441
16803
|
paths.historyDir,
|
|
16442
16804
|
prevActive ?? "(none)",
|
|
@@ -16454,7 +16816,20 @@ async function applyStack(paths, name, options = {}) {
|
|
|
16454
16816
|
version: 1,
|
|
16455
16817
|
active: name,
|
|
16456
16818
|
previousActive: prevActive,
|
|
16457
|
-
lastSwitchedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
16819
|
+
lastSwitchedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
16820
|
+
fallbackAgents: Object.fromEntries(
|
|
16821
|
+
Object.entries(target.agents).filter(([, entry]) => entry.fallbacks?.length).map(([agent, entry]) => {
|
|
16822
|
+
const variant = "variant" in entry ? entry.variant : displaced.agents[agent]?.variant;
|
|
16823
|
+
return [
|
|
16824
|
+
agent,
|
|
16825
|
+
{
|
|
16826
|
+
model: entry.model,
|
|
16827
|
+
variant: typeof variant === "string" ? variant : void 0,
|
|
16828
|
+
fallbacks: entry.fallbacks
|
|
16829
|
+
}
|
|
16830
|
+
];
|
|
16831
|
+
})
|
|
16832
|
+
)
|
|
16458
16833
|
});
|
|
16459
16834
|
await trimHistory(paths.historyDir).catch(() => {
|
|
16460
16835
|
});
|
|
@@ -16466,14 +16841,40 @@ async function applyStack(paths, name, options = {}) {
|
|
|
16466
16841
|
restartRequired: true
|
|
16467
16842
|
};
|
|
16468
16843
|
}
|
|
16469
|
-
function
|
|
16844
|
+
function entryOptions(entry) {
|
|
16845
|
+
const rec = entry;
|
|
16846
|
+
const out = {};
|
|
16847
|
+
for (const [k, v] of Object.entries(rec)) {
|
|
16848
|
+
if (k === "model" || k === "fallbacks" || RESERVED_AGENT_KEYS.has(k)) continue;
|
|
16849
|
+
out[k] = v;
|
|
16850
|
+
}
|
|
16851
|
+
return out;
|
|
16852
|
+
}
|
|
16853
|
+
function entriesToStackAgents(entries) {
|
|
16470
16854
|
const out = {};
|
|
16471
|
-
for (const
|
|
16472
|
-
const
|
|
16473
|
-
if (
|
|
16855
|
+
for (const name of Object.keys(entries).sort()) {
|
|
16856
|
+
const entry = entries[name];
|
|
16857
|
+
if (!entry) continue;
|
|
16858
|
+
const { model, options } = entry;
|
|
16859
|
+
const stackEntry = { model };
|
|
16860
|
+
for (const [k, v] of Object.entries(options)) {
|
|
16861
|
+
if (k !== "fallbacks") stackEntry[k] = v;
|
|
16862
|
+
}
|
|
16863
|
+
out[name] = stackEntry;
|
|
16474
16864
|
}
|
|
16475
16865
|
return out;
|
|
16476
16866
|
}
|
|
16867
|
+
async function captureAgents(paths) {
|
|
16868
|
+
const agents = entriesToStackAgents(await readAgentEntries(paths.agentsDir));
|
|
16869
|
+
const state = await readState(paths.statePath);
|
|
16870
|
+
for (const [name, entry] of Object.entries(agents)) {
|
|
16871
|
+
const routing = state?.fallbackAgents?.[name];
|
|
16872
|
+
if (routing && routing.model === entry.model && (routing.variant ?? void 0) === (entry.variant ?? void 0)) {
|
|
16873
|
+
entry.fallbacks = routing.fallbacks;
|
|
16874
|
+
}
|
|
16875
|
+
}
|
|
16876
|
+
return agents;
|
|
16877
|
+
}
|
|
16477
16878
|
async function back(paths, n = 1, options = {}) {
|
|
16478
16879
|
if (n < 1) throw new UserError("`back -n` must be at least 1.");
|
|
16479
16880
|
const state = await readState(paths.statePath);
|
|
@@ -16507,8 +16908,7 @@ async function captureStack(paths, name, options = {}) {
|
|
|
16507
16908
|
if (existsSync6(dest) && !options.force) {
|
|
16508
16909
|
throw new UserError(`Stack "${name}" already exists. Use --force to overwrite.`);
|
|
16509
16910
|
}
|
|
16510
|
-
const
|
|
16511
|
-
const agents = modelsToStackAgents(models);
|
|
16911
|
+
const agents = await captureAgents(paths);
|
|
16512
16912
|
if (Object.keys(agents).length === 0) {
|
|
16513
16913
|
throw new UserError(
|
|
16514
16914
|
`No agent .md files with a frontmatter \`model:\` line found in ${paths.agentsDir}.`
|
|
@@ -16551,7 +16951,7 @@ async function exportStack(paths, name, toFile) {
|
|
|
16551
16951
|
}
|
|
16552
16952
|
|
|
16553
16953
|
// src/version.ts
|
|
16554
|
-
var VERSION = "1.0
|
|
16954
|
+
var VERSION = "1.1.0";
|
|
16555
16955
|
|
|
16556
16956
|
// src/cli.ts
|
|
16557
16957
|
var log = (...args) => console.log(...args);
|
|
@@ -16706,11 +17106,7 @@ async function cmdValidate(paths, name, opts) {
|
|
|
16706
17106
|
targets.push({
|
|
16707
17107
|
name: "(current frontmatter)",
|
|
16708
17108
|
load: async () => {
|
|
16709
|
-
|
|
16710
|
-
const agents = Object.fromEntries(
|
|
16711
|
-
Object.entries(models).map(([k, model]) => [k, { model }])
|
|
16712
|
-
);
|
|
16713
|
-
return { agents };
|
|
17109
|
+
return { agents: await captureAgents(paths) };
|
|
16714
17110
|
}
|
|
16715
17111
|
});
|
|
16716
17112
|
} else if (opts.all) {
|