@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/tui.js
CHANGED
|
@@ -14615,14 +14615,26 @@ function date4(params) {
|
|
|
14615
14615
|
config(en_default());
|
|
14616
14616
|
|
|
14617
14617
|
// src/core/schema.ts
|
|
14618
|
+
var FallbackSchema = external_exports.object({
|
|
14619
|
+
model: external_exports.string().regex(/^[^/\s]+\/\S+$/, "Expected provider/model"),
|
|
14620
|
+
variant: external_exports.string().min(1).optional()
|
|
14621
|
+
}).strict();
|
|
14622
|
+
var RoutingEntrySchema = external_exports.object({
|
|
14623
|
+
model: external_exports.string().min(1),
|
|
14624
|
+
variant: external_exports.string().min(1).nullable().optional(),
|
|
14625
|
+
fallbacks: external_exports.array(FallbackSchema).max(8).optional()
|
|
14626
|
+
}).strict();
|
|
14618
14627
|
var StateFileSchema = external_exports.object({
|
|
14619
14628
|
version: external_exports.literal(1),
|
|
14620
14629
|
active: external_exports.string().min(1),
|
|
14621
14630
|
previousActive: external_exports.string().min(1).nullable(),
|
|
14622
|
-
lastSwitchedAt: external_exports.string().min(1)
|
|
14631
|
+
lastSwitchedAt: external_exports.string().min(1),
|
|
14632
|
+
fallbackAgents: external_exports.record(external_exports.string(), RoutingEntrySchema).optional()
|
|
14623
14633
|
}).strict();
|
|
14624
14634
|
var AgentEntrySchema = external_exports.object({
|
|
14625
|
-
model: external_exports.string().min(1)
|
|
14635
|
+
model: external_exports.string().min(1),
|
|
14636
|
+
variant: external_exports.string().min(1).nullable().optional(),
|
|
14637
|
+
fallbacks: external_exports.array(FallbackSchema).max(8).optional()
|
|
14626
14638
|
}).passthrough();
|
|
14627
14639
|
var StackFileSchema = external_exports.object({
|
|
14628
14640
|
agents: external_exports.record(external_exports.string(), AgentEntrySchema)
|
|
@@ -14746,8 +14758,300 @@ import path7 from "path";
|
|
|
14746
14758
|
import { existsSync as existsSync2 } from "fs";
|
|
14747
14759
|
import { readFile as readFile2, readdir } from "fs/promises";
|
|
14748
14760
|
import path4 from "path";
|
|
14761
|
+
|
|
14762
|
+
// src/core/yaml-lite.ts
|
|
14763
|
+
var RESERVED_WORDS = /* @__PURE__ */ new Set([
|
|
14764
|
+
"null",
|
|
14765
|
+
"true",
|
|
14766
|
+
"false",
|
|
14767
|
+
"yes",
|
|
14768
|
+
"no",
|
|
14769
|
+
"on",
|
|
14770
|
+
"off",
|
|
14771
|
+
"~",
|
|
14772
|
+
// YAML 1.1 nulls
|
|
14773
|
+
"Null",
|
|
14774
|
+
"NULL",
|
|
14775
|
+
"True",
|
|
14776
|
+
"False",
|
|
14777
|
+
"Yes",
|
|
14778
|
+
"No",
|
|
14779
|
+
"On",
|
|
14780
|
+
"Off"
|
|
14781
|
+
]);
|
|
14782
|
+
var BARE_STRING_RE = /^[A-Za-z0-9._\-\/+@$%^&()~]+$/;
|
|
14783
|
+
function needsQuoting(s) {
|
|
14784
|
+
if (s === "") return true;
|
|
14785
|
+
if (RESERVED_WORDS.has(s)) return true;
|
|
14786
|
+
if (/^[+-]?(\d+\.?\d*|\.\d+)([eE][+-]?\d+)?$/.test(s)) return true;
|
|
14787
|
+
if (/^0x[0-9A-Fa-f]+$/.test(s)) return true;
|
|
14788
|
+
if (/^[+-]?\.(inf|Inf|INF|nan|NaN|NAN)$/.test(s)) return true;
|
|
14789
|
+
if (!BARE_STRING_RE.test(s)) return true;
|
|
14790
|
+
return false;
|
|
14791
|
+
}
|
|
14792
|
+
function serializeOptionValue(value) {
|
|
14793
|
+
if (value === null || value === void 0) return "null";
|
|
14794
|
+
if (typeof value === "boolean") return value ? "true" : "false";
|
|
14795
|
+
if (typeof value === "number") {
|
|
14796
|
+
if (!Number.isFinite(value)) return String(value);
|
|
14797
|
+
return JSON.stringify(value);
|
|
14798
|
+
}
|
|
14799
|
+
if (typeof value === "bigint") return String(value);
|
|
14800
|
+
if (typeof value === "string") return needsQuoting(value) ? JSON.stringify(value) : value;
|
|
14801
|
+
return JSON.stringify(value);
|
|
14802
|
+
}
|
|
14803
|
+
function stripComment(line) {
|
|
14804
|
+
let inSingle = false;
|
|
14805
|
+
let inDouble = false;
|
|
14806
|
+
for (let i = 0; i < line.length; i++) {
|
|
14807
|
+
const ch = line[i];
|
|
14808
|
+
if (ch === "'" && !inDouble) inSingle = !inSingle;
|
|
14809
|
+
else if (ch === '"' && !inSingle) inDouble = !inDouble;
|
|
14810
|
+
else if (ch === "#" && !inSingle && !inDouble) {
|
|
14811
|
+
if (i === 0 || /\s/.test(line[i - 1] ?? "")) return line.slice(0, i);
|
|
14812
|
+
}
|
|
14813
|
+
}
|
|
14814
|
+
return line;
|
|
14815
|
+
}
|
|
14816
|
+
function tokenize(block) {
|
|
14817
|
+
const out = [];
|
|
14818
|
+
for (const raw of block.split(/\r?\n/)) {
|
|
14819
|
+
if (raw.trim() === "") continue;
|
|
14820
|
+
const indentMatch = /^( *)/.exec(raw);
|
|
14821
|
+
const indent = indentMatch?.[1]?.length ?? 0;
|
|
14822
|
+
const content = stripComment(raw.slice(indent));
|
|
14823
|
+
if (content.trim() === "") continue;
|
|
14824
|
+
out.push({ indent, text: content.trimEnd(), raw });
|
|
14825
|
+
}
|
|
14826
|
+
return out;
|
|
14827
|
+
}
|
|
14828
|
+
function coerceScalar(raw) {
|
|
14829
|
+
const v = raw.trim();
|
|
14830
|
+
if (v === "" || v === "null" || v === "~" || v === "Null" || v === "NULL") return null;
|
|
14831
|
+
if (v === "true" || v === "True" || v === "TRUE") return true;
|
|
14832
|
+
if (v === "false" || v === "False" || v === "FALSE") return false;
|
|
14833
|
+
if (v === "yes" || v === "Yes" || v === "YES") return true;
|
|
14834
|
+
if (v === "no" || v === "No" || v === "NO") return false;
|
|
14835
|
+
if (v === "on" || v === "On" || v === "ON") return true;
|
|
14836
|
+
if (v === "off" || v === "Off" || v === "OFF") return false;
|
|
14837
|
+
if (/^[+-]?\d+$/.test(v)) return Number.parseInt(v, 10);
|
|
14838
|
+
if (/^[+-]?(\d+\.?\d*|\.\d+)([eE][+-]?\d+)?$/.test(v)) return Number.parseFloat(v);
|
|
14839
|
+
if (/^0x[0-9A-Fa-f]+$/.test(v)) return Number.parseInt(v, 16);
|
|
14840
|
+
return v;
|
|
14841
|
+
}
|
|
14842
|
+
function parseQuoted(s) {
|
|
14843
|
+
const quote = s[0] ?? "";
|
|
14844
|
+
if (quote !== '"' && quote !== "'") return null;
|
|
14845
|
+
if (quote === '"') {
|
|
14846
|
+
let out2 = "";
|
|
14847
|
+
for (let i = 1; i < s.length; i++) {
|
|
14848
|
+
const ch = s[i] ?? "";
|
|
14849
|
+
if (ch === "\\" && i + 1 < s.length) {
|
|
14850
|
+
const next = s[i + 1] ?? "";
|
|
14851
|
+
const map2 = {
|
|
14852
|
+
n: "\n",
|
|
14853
|
+
t: " ",
|
|
14854
|
+
r: "\r",
|
|
14855
|
+
'"': '"',
|
|
14856
|
+
"\\": "\\",
|
|
14857
|
+
"/": "/",
|
|
14858
|
+
b: "\b",
|
|
14859
|
+
f: "\f"
|
|
14860
|
+
};
|
|
14861
|
+
out2 += map2[next] ?? next;
|
|
14862
|
+
i++;
|
|
14863
|
+
} else if (ch === '"') {
|
|
14864
|
+
return { value: out2, rest: s.slice(i + 1) };
|
|
14865
|
+
} else {
|
|
14866
|
+
out2 += ch;
|
|
14867
|
+
}
|
|
14868
|
+
}
|
|
14869
|
+
return null;
|
|
14870
|
+
}
|
|
14871
|
+
let out = "";
|
|
14872
|
+
for (let i = 1; i < s.length; i++) {
|
|
14873
|
+
const ch = s[i] ?? "";
|
|
14874
|
+
if (ch === "'") {
|
|
14875
|
+
if (s[i + 1] === "'") {
|
|
14876
|
+
out += "'";
|
|
14877
|
+
i++;
|
|
14878
|
+
} else {
|
|
14879
|
+
return { value: out, rest: s.slice(i + 1) };
|
|
14880
|
+
}
|
|
14881
|
+
} else {
|
|
14882
|
+
out += ch;
|
|
14883
|
+
}
|
|
14884
|
+
}
|
|
14885
|
+
return null;
|
|
14886
|
+
}
|
|
14887
|
+
function splitFlow(raw) {
|
|
14888
|
+
const parts = [];
|
|
14889
|
+
let depth = 0;
|
|
14890
|
+
let inSingle = false;
|
|
14891
|
+
let inDouble = false;
|
|
14892
|
+
let start = 0;
|
|
14893
|
+
for (let i = 0; i < raw.length; i++) {
|
|
14894
|
+
const ch = raw[i];
|
|
14895
|
+
if (ch === "'" && !inDouble) inSingle = !inSingle;
|
|
14896
|
+
else if (ch === '"' && !inSingle) inDouble = !inDouble;
|
|
14897
|
+
else if (!inSingle && !inDouble) {
|
|
14898
|
+
if (ch === "{" || ch === "[") depth++;
|
|
14899
|
+
else if (ch === "}" || ch === "]") depth--;
|
|
14900
|
+
else if (ch === "," && depth === 0) {
|
|
14901
|
+
parts.push(raw.slice(start, i));
|
|
14902
|
+
start = i + 1;
|
|
14903
|
+
}
|
|
14904
|
+
}
|
|
14905
|
+
}
|
|
14906
|
+
parts.push(raw.slice(start));
|
|
14907
|
+
return parts;
|
|
14908
|
+
}
|
|
14909
|
+
function parseFlowValue(raw) {
|
|
14910
|
+
const s = raw.trim();
|
|
14911
|
+
if (s === "") return null;
|
|
14912
|
+
const first = s[0] ?? "";
|
|
14913
|
+
if (first === "{") {
|
|
14914
|
+
const inner = s.slice(1, s.length - 1);
|
|
14915
|
+
const obj = {};
|
|
14916
|
+
for (const part of splitFlow(inner)) {
|
|
14917
|
+
const t = part.trim();
|
|
14918
|
+
if (t === "") continue;
|
|
14919
|
+
const colon = findColon(t);
|
|
14920
|
+
if (colon < 0) {
|
|
14921
|
+
obj[t] = null;
|
|
14922
|
+
} else {
|
|
14923
|
+
const k = t.slice(0, colon).trim();
|
|
14924
|
+
const v = t.slice(colon + 1).trim();
|
|
14925
|
+
obj[stripKeyQuotes(k)] = parseFlowValue(v);
|
|
14926
|
+
}
|
|
14927
|
+
}
|
|
14928
|
+
return obj;
|
|
14929
|
+
}
|
|
14930
|
+
if (first === "[") {
|
|
14931
|
+
const inner = s.slice(1, s.length - 1);
|
|
14932
|
+
return splitFlow(inner).filter((p) => p.trim() !== "").map((p) => parseFlowValue(p));
|
|
14933
|
+
}
|
|
14934
|
+
if (first === '"' || first === "'") {
|
|
14935
|
+
const q = parseQuoted(s);
|
|
14936
|
+
return q ? q.value : s;
|
|
14937
|
+
}
|
|
14938
|
+
return coerceScalar(s);
|
|
14939
|
+
}
|
|
14940
|
+
function findColon(s) {
|
|
14941
|
+
let inSingle = false;
|
|
14942
|
+
let inDouble = false;
|
|
14943
|
+
let depth = 0;
|
|
14944
|
+
for (let i = 0; i < s.length; i++) {
|
|
14945
|
+
const ch = s[i];
|
|
14946
|
+
if (ch === "'" && !inDouble) inSingle = !inSingle;
|
|
14947
|
+
else if (ch === '"' && !inSingle) inDouble = !inDouble;
|
|
14948
|
+
else if (!inSingle && !inDouble) {
|
|
14949
|
+
if (ch === "{" || ch === "[") depth++;
|
|
14950
|
+
else if (ch === "}" || ch === "]") depth--;
|
|
14951
|
+
else if (ch === ":" && depth === 0) return i;
|
|
14952
|
+
}
|
|
14953
|
+
}
|
|
14954
|
+
return -1;
|
|
14955
|
+
}
|
|
14956
|
+
function stripKeyQuotes(k) {
|
|
14957
|
+
if (k.length >= 2 && (k[0] === '"' && k.at(-1) === '"' || k[0] === "'" && k.at(-1) === "'")) {
|
|
14958
|
+
return k.slice(1, -1);
|
|
14959
|
+
}
|
|
14960
|
+
return k;
|
|
14961
|
+
}
|
|
14962
|
+
function parseMapping(lines, startIdx, indent) {
|
|
14963
|
+
const out = {};
|
|
14964
|
+
let i = startIdx;
|
|
14965
|
+
while (i < lines.length) {
|
|
14966
|
+
const line = lines[i];
|
|
14967
|
+
if (!line) break;
|
|
14968
|
+
if (line.indent < indent) break;
|
|
14969
|
+
if (line.indent > indent) {
|
|
14970
|
+
i++;
|
|
14971
|
+
continue;
|
|
14972
|
+
}
|
|
14973
|
+
const colon = findColon(line.text);
|
|
14974
|
+
if (colon < 0) {
|
|
14975
|
+
i++;
|
|
14976
|
+
continue;
|
|
14977
|
+
}
|
|
14978
|
+
const key = stripKeyQuotes(line.text.slice(0, colon).trim());
|
|
14979
|
+
if (key === "") {
|
|
14980
|
+
i++;
|
|
14981
|
+
continue;
|
|
14982
|
+
}
|
|
14983
|
+
const rest = line.text.slice(colon + 1).trim();
|
|
14984
|
+
if (rest !== "") {
|
|
14985
|
+
out[key] = parseFlowValue(rest);
|
|
14986
|
+
i++;
|
|
14987
|
+
} else {
|
|
14988
|
+
const childIndent = lines[i + 1]?.indent ?? line.indent + 1;
|
|
14989
|
+
let j = i + 1;
|
|
14990
|
+
while (j < lines.length && (lines[j]?.indent ?? -1) >= childIndent) j++;
|
|
14991
|
+
if (j === i + 1) {
|
|
14992
|
+
out[key] = null;
|
|
14993
|
+
i++;
|
|
14994
|
+
} else {
|
|
14995
|
+
const childBlock = lines.slice(i + 1, j);
|
|
14996
|
+
const firstChild = childBlock[0];
|
|
14997
|
+
if (firstChild?.text.trimStart().startsWith("- ")) {
|
|
14998
|
+
out[key] = parseSequence(childBlock, childIndent);
|
|
14999
|
+
} else {
|
|
15000
|
+
out[key] = parseMapping(childBlock, 0, childIndent).value;
|
|
15001
|
+
}
|
|
15002
|
+
i = j;
|
|
15003
|
+
}
|
|
15004
|
+
}
|
|
15005
|
+
}
|
|
15006
|
+
return { value: out, next: i };
|
|
15007
|
+
}
|
|
15008
|
+
function parseSequence(lines, indent) {
|
|
15009
|
+
const out = [];
|
|
15010
|
+
let i = 0;
|
|
15011
|
+
while (i < lines.length) {
|
|
15012
|
+
const line = lines[i];
|
|
15013
|
+
if (!line) break;
|
|
15014
|
+
if (line.indent < indent) break;
|
|
15015
|
+
const t = line.text.trimStart();
|
|
15016
|
+
if (!t.startsWith("- ")) {
|
|
15017
|
+
i++;
|
|
15018
|
+
continue;
|
|
15019
|
+
}
|
|
15020
|
+
const item = t.slice(2).trim();
|
|
15021
|
+
if (item === "") {
|
|
15022
|
+
const childIndent = lines[i + 1]?.indent ?? line.indent + 1;
|
|
15023
|
+
let j = i + 1;
|
|
15024
|
+
while (j < lines.length && (lines[j]?.indent ?? -1) >= childIndent) j++;
|
|
15025
|
+
out.push(parseMapping(lines.slice(i + 1, j), 0, childIndent).value);
|
|
15026
|
+
i = j;
|
|
15027
|
+
} else {
|
|
15028
|
+
out.push(parseFlowValue(item));
|
|
15029
|
+
i++;
|
|
15030
|
+
}
|
|
15031
|
+
}
|
|
15032
|
+
return out;
|
|
15033
|
+
}
|
|
15034
|
+
function parseFrontmatterBlock(block) {
|
|
15035
|
+
const lines = tokenize(block);
|
|
15036
|
+
if (lines.length === 0) return null;
|
|
15037
|
+
const { value } = parseMapping(lines, 0, 0);
|
|
15038
|
+
return value;
|
|
15039
|
+
}
|
|
15040
|
+
|
|
15041
|
+
// src/core/frontmatter.ts
|
|
14749
15042
|
var FRONTMATTER_RE = /^---\r?\n([\s\S]*?)\r?\n---(?:\r?\n|$)/;
|
|
14750
15043
|
var MODEL_LINE_RE = /^model:[ \t]*(.*)$/m;
|
|
15044
|
+
var RESERVED_AGENT_KEYS = /* @__PURE__ */ new Set([
|
|
15045
|
+
"name",
|
|
15046
|
+
"mode",
|
|
15047
|
+
"description",
|
|
15048
|
+
"permission",
|
|
15049
|
+
"color",
|
|
15050
|
+
"tools",
|
|
15051
|
+
"prompt",
|
|
15052
|
+
"steps",
|
|
15053
|
+
"maxSteps"
|
|
15054
|
+
]);
|
|
14751
15055
|
function cleanModelValue(raw) {
|
|
14752
15056
|
let v = raw;
|
|
14753
15057
|
const hash2 = v.search(/[ \t]#/);
|
|
@@ -14776,6 +15080,55 @@ function setFrontmatterModel(content, model) {
|
|
|
14776
15080
|
const nextFm = fm[0].slice(0, blockStart) + nextBlock + fm[0].slice(blockStart + block.length);
|
|
14777
15081
|
return nextFm + content.slice(fm[0].length);
|
|
14778
15082
|
}
|
|
15083
|
+
function getFrontmatterOptions(content) {
|
|
15084
|
+
const fm = FRONTMATTER_RE.exec(content);
|
|
15085
|
+
if (!fm?.[1]) return {};
|
|
15086
|
+
const parsed = parseFrontmatterBlock(fm[1]);
|
|
15087
|
+
if (!parsed) return {};
|
|
15088
|
+
const out = {};
|
|
15089
|
+
for (const [k, v] of Object.entries(parsed)) {
|
|
15090
|
+
if (k === "model" || RESERVED_AGENT_KEYS.has(k)) continue;
|
|
15091
|
+
out[k] = v;
|
|
15092
|
+
}
|
|
15093
|
+
return out;
|
|
15094
|
+
}
|
|
15095
|
+
function setFrontmatterOptions(content, options) {
|
|
15096
|
+
const fm = FRONTMATTER_RE.exec(content);
|
|
15097
|
+
if (!fm?.[1]) throw new Error("no frontmatter block");
|
|
15098
|
+
const block = fm[1];
|
|
15099
|
+
const keys = Object.keys(options);
|
|
15100
|
+
if (keys.length === 0) return content;
|
|
15101
|
+
const eol = block.includes("\r\n") ? "\r\n" : "\n";
|
|
15102
|
+
const lines = block.split(/\r?\n/);
|
|
15103
|
+
const handled = /* @__PURE__ */ new Set();
|
|
15104
|
+
for (let i = 0; i < lines.length; i++) {
|
|
15105
|
+
const m = /^([A-Za-z0-9_-]+):[ \t]*(.*)$/.exec(lines[i] ?? "");
|
|
15106
|
+
if (!m) continue;
|
|
15107
|
+
const key = m[1] ?? "";
|
|
15108
|
+
if (!(key in options)) continue;
|
|
15109
|
+
handled.add(key);
|
|
15110
|
+
let end = i + 1;
|
|
15111
|
+
while (end < lines.length && /^[ \t]+/.test(lines[end] ?? "")) end++;
|
|
15112
|
+
const value = options[key];
|
|
15113
|
+
if (value === null || value === void 0) {
|
|
15114
|
+
lines.splice(i, end - i);
|
|
15115
|
+
i--;
|
|
15116
|
+
} else {
|
|
15117
|
+
lines.splice(i, end - i, `${key}: ${serializeOptionValue(value)}`);
|
|
15118
|
+
}
|
|
15119
|
+
}
|
|
15120
|
+
const appended = [];
|
|
15121
|
+
for (const [key, value] of Object.entries(options)) {
|
|
15122
|
+
if (handled.has(key)) continue;
|
|
15123
|
+
if (value === null || value === void 0) continue;
|
|
15124
|
+
appended.push(`${key}: ${serializeOptionValue(value)}`);
|
|
15125
|
+
}
|
|
15126
|
+
const nextBlock = appended.length > 0 ? lines.join(eol) + eol + appended.join(eol) : lines.join(eol);
|
|
15127
|
+
if (nextBlock === block) return content;
|
|
15128
|
+
const blockStart = fm[0].indexOf(block);
|
|
15129
|
+
const nextFm = fm[0].slice(0, blockStart) + nextBlock + fm[0].slice(blockStart + block.length);
|
|
15130
|
+
return nextFm + content.slice(fm[0].length);
|
|
15131
|
+
}
|
|
14779
15132
|
function agentFilePath(agentsDir, name) {
|
|
14780
15133
|
return path4.join(agentsDir, `${name}.md`);
|
|
14781
15134
|
}
|
|
@@ -14789,7 +15142,7 @@ async function listAgentFiles(agentsDir) {
|
|
|
14789
15142
|
}
|
|
14790
15143
|
return names.filter((n) => n.endsWith(".md")).map((n) => n.slice(0, -".md".length)).sort();
|
|
14791
15144
|
}
|
|
14792
|
-
async function
|
|
15145
|
+
async function readAgentEntries(agentsDir) {
|
|
14793
15146
|
const out = {};
|
|
14794
15147
|
for (const name of await listAgentFiles(agentsDir)) {
|
|
14795
15148
|
const filePath = agentFilePath(agentsDir, name);
|
|
@@ -14800,7 +15153,8 @@ async function readAgentModels(agentsDir) {
|
|
|
14800
15153
|
throw new IOError(`Failed to read ${filePath}: ${cause.message}`, cause);
|
|
14801
15154
|
}
|
|
14802
15155
|
const model = getFrontmatterModel(content);
|
|
14803
|
-
if (model
|
|
15156
|
+
if (model === null) continue;
|
|
15157
|
+
out[name] = { model, options: getFrontmatterOptions(content) };
|
|
14804
15158
|
}
|
|
14805
15159
|
return out;
|
|
14806
15160
|
}
|
|
@@ -14819,7 +15173,7 @@ async function readAgentFileStrict(agentsDir, name) {
|
|
|
14819
15173
|
if (model === null) {
|
|
14820
15174
|
throw new AgentFileError(name, filePath, "no frontmatter `model:` line to rewrite");
|
|
14821
15175
|
}
|
|
14822
|
-
return { filePath, content, model };
|
|
15176
|
+
return { filePath, content, model, options: getFrontmatterOptions(content) };
|
|
14823
15177
|
}
|
|
14824
15178
|
|
|
14825
15179
|
// src/core/history.ts
|
|
@@ -14961,6 +15315,9 @@ function collectModelRefs(stack) {
|
|
|
14961
15315
|
const refs = [];
|
|
14962
15316
|
for (const [k, v] of Object.entries(stack.agents)) {
|
|
14963
15317
|
refs.push({ path: `agents.${k}.model`, modelId: v.model });
|
|
15318
|
+
for (const [index, candidate] of (v.fallbacks ?? []).entries()) {
|
|
15319
|
+
refs.push({ path: `agents.${k}.fallbacks.${index}.model`, modelId: candidate.model });
|
|
15320
|
+
}
|
|
14964
15321
|
}
|
|
14965
15322
|
return refs;
|
|
14966
15323
|
}
|
|
@@ -15033,15 +15390,14 @@ async function applyStack(paths, name, options = {}) {
|
|
|
15033
15390
|
const pending = [];
|
|
15034
15391
|
for (const [agent, entry] of Object.entries(target.agents)) {
|
|
15035
15392
|
const { filePath, content, model } = await readAgentFileStrict(paths.agentsDir, agent);
|
|
15036
|
-
|
|
15037
|
-
|
|
15038
|
-
|
|
15039
|
-
|
|
15040
|
-
});
|
|
15393
|
+
const wantOptions = entryOptions(entry);
|
|
15394
|
+
const afterOptions = setFrontmatterOptions(content, wantOptions);
|
|
15395
|
+
const nextContent = entry.model === model ? afterOptions : setFrontmatterModel(afterOptions, entry.model);
|
|
15396
|
+
pending.push({ agent, filePath, next: nextContent === content ? null : nextContent });
|
|
15041
15397
|
}
|
|
15042
15398
|
const prevState = await readState(paths.statePath);
|
|
15043
15399
|
const prevActive = prevState?.active ?? null;
|
|
15044
|
-
const displaced = { agents:
|
|
15400
|
+
const displaced = { agents: await captureAgents(paths) };
|
|
15045
15401
|
const historyId = await appendHistory(
|
|
15046
15402
|
paths.historyDir,
|
|
15047
15403
|
prevActive ?? "(none)",
|
|
@@ -15059,7 +15415,20 @@ async function applyStack(paths, name, options = {}) {
|
|
|
15059
15415
|
version: 1,
|
|
15060
15416
|
active: name,
|
|
15061
15417
|
previousActive: prevActive,
|
|
15062
|
-
lastSwitchedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
15418
|
+
lastSwitchedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
15419
|
+
fallbackAgents: Object.fromEntries(
|
|
15420
|
+
Object.entries(target.agents).filter(([, entry]) => entry.fallbacks?.length).map(([agent, entry]) => {
|
|
15421
|
+
const variant = "variant" in entry ? entry.variant : displaced.agents[agent]?.variant;
|
|
15422
|
+
return [
|
|
15423
|
+
agent,
|
|
15424
|
+
{
|
|
15425
|
+
model: entry.model,
|
|
15426
|
+
variant: typeof variant === "string" ? variant : void 0,
|
|
15427
|
+
fallbacks: entry.fallbacks
|
|
15428
|
+
}
|
|
15429
|
+
];
|
|
15430
|
+
})
|
|
15431
|
+
)
|
|
15063
15432
|
});
|
|
15064
15433
|
await trimHistory(paths.historyDir).catch(() => {
|
|
15065
15434
|
});
|
|
@@ -15071,14 +15440,40 @@ async function applyStack(paths, name, options = {}) {
|
|
|
15071
15440
|
restartRequired: true
|
|
15072
15441
|
};
|
|
15073
15442
|
}
|
|
15074
|
-
function
|
|
15443
|
+
function entryOptions(entry) {
|
|
15444
|
+
const rec = entry;
|
|
15075
15445
|
const out = {};
|
|
15076
|
-
for (const k of Object.
|
|
15077
|
-
|
|
15078
|
-
|
|
15446
|
+
for (const [k, v] of Object.entries(rec)) {
|
|
15447
|
+
if (k === "model" || k === "fallbacks" || RESERVED_AGENT_KEYS.has(k)) continue;
|
|
15448
|
+
out[k] = v;
|
|
15079
15449
|
}
|
|
15080
15450
|
return out;
|
|
15081
15451
|
}
|
|
15452
|
+
function entriesToStackAgents(entries) {
|
|
15453
|
+
const out = {};
|
|
15454
|
+
for (const name of Object.keys(entries).sort()) {
|
|
15455
|
+
const entry = entries[name];
|
|
15456
|
+
if (!entry) continue;
|
|
15457
|
+
const { model, options } = entry;
|
|
15458
|
+
const stackEntry = { model };
|
|
15459
|
+
for (const [k, v] of Object.entries(options)) {
|
|
15460
|
+
if (k !== "fallbacks") stackEntry[k] = v;
|
|
15461
|
+
}
|
|
15462
|
+
out[name] = stackEntry;
|
|
15463
|
+
}
|
|
15464
|
+
return out;
|
|
15465
|
+
}
|
|
15466
|
+
async function captureAgents(paths) {
|
|
15467
|
+
const agents = entriesToStackAgents(await readAgentEntries(paths.agentsDir));
|
|
15468
|
+
const state = await readState(paths.statePath);
|
|
15469
|
+
for (const [name, entry] of Object.entries(agents)) {
|
|
15470
|
+
const routing = state?.fallbackAgents?.[name];
|
|
15471
|
+
if (routing && routing.model === entry.model && (routing.variant ?? void 0) === (entry.variant ?? void 0)) {
|
|
15472
|
+
entry.fallbacks = routing.fallbacks;
|
|
15473
|
+
}
|
|
15474
|
+
}
|
|
15475
|
+
return agents;
|
|
15476
|
+
}
|
|
15082
15477
|
async function back(paths, n = 1, options = {}) {
|
|
15083
15478
|
if (n < 1) throw new UserError("`back -n` must be at least 1.");
|
|
15084
15479
|
const state = await readState(paths.statePath);
|