neuralos 3.2.16 → 3.2.18
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/bin/gybackend.cjs +518 -250
- package/package.json +2 -2
package/bin/gybackend.cjs
CHANGED
|
@@ -255452,6 +255452,256 @@ var require_pdf_parse2 = __commonJS({
|
|
|
255452
255452
|
}
|
|
255453
255453
|
});
|
|
255454
255454
|
|
|
255455
|
+
// ../../packages/backend/src/services/automation/templateEngine.ts
|
|
255456
|
+
var templateEngine_exports = {};
|
|
255457
|
+
__export(templateEngine_exports, {
|
|
255458
|
+
diffStrings: () => diffStrings,
|
|
255459
|
+
renderTemplate: () => renderTemplate2
|
|
255460
|
+
});
|
|
255461
|
+
function resolve(expr, scope) {
|
|
255462
|
+
const parts = expr.trim().split(".");
|
|
255463
|
+
let cur = scope;
|
|
255464
|
+
for (const p of parts) {
|
|
255465
|
+
if (cur == null) return void 0;
|
|
255466
|
+
if (typeof cur === "object") {
|
|
255467
|
+
cur = cur[p];
|
|
255468
|
+
} else {
|
|
255469
|
+
return void 0;
|
|
255470
|
+
}
|
|
255471
|
+
}
|
|
255472
|
+
return cur;
|
|
255473
|
+
}
|
|
255474
|
+
function applyFilters(value, filters, scope) {
|
|
255475
|
+
let v = value;
|
|
255476
|
+
for (const f of filters) {
|
|
255477
|
+
const m2 = f.match(/^(\w+)\s*(?:\((.*)\))?$/);
|
|
255478
|
+
if (!m2) continue;
|
|
255479
|
+
const name = m2[1];
|
|
255480
|
+
const argRaw = m2[2] ?? "";
|
|
255481
|
+
const arg = argRaw.replace(/^['"]|['"]$/g, "");
|
|
255482
|
+
switch (name) {
|
|
255483
|
+
case "default":
|
|
255484
|
+
if (v === void 0 || v === null || v === "") {
|
|
255485
|
+
v = arg in scope ? resolve(arg, scope) : arg;
|
|
255486
|
+
}
|
|
255487
|
+
break;
|
|
255488
|
+
case "upper":
|
|
255489
|
+
v = String(v ?? "").toUpperCase();
|
|
255490
|
+
break;
|
|
255491
|
+
case "lower":
|
|
255492
|
+
v = String(v ?? "").toLowerCase();
|
|
255493
|
+
break;
|
|
255494
|
+
case "length":
|
|
255495
|
+
if (Array.isArray(v)) v = v.length;
|
|
255496
|
+
else if (typeof v === "string") v = v.length;
|
|
255497
|
+
else v = 0;
|
|
255498
|
+
break;
|
|
255499
|
+
default:
|
|
255500
|
+
break;
|
|
255501
|
+
}
|
|
255502
|
+
}
|
|
255503
|
+
return v;
|
|
255504
|
+
}
|
|
255505
|
+
function evalExpr(expr, scope) {
|
|
255506
|
+
const parts = expr.split("|").map((p) => p.trim());
|
|
255507
|
+
const varExpr = parts[0];
|
|
255508
|
+
const filters = parts.slice(1);
|
|
255509
|
+
let val = resolve(varExpr, scope);
|
|
255510
|
+
val = applyFilters(val, filters, scope);
|
|
255511
|
+
if (val === void 0 || val === null) return "";
|
|
255512
|
+
if (Array.isArray(val)) return val.join(", ");
|
|
255513
|
+
if (typeof val === "object") return JSON.stringify(val);
|
|
255514
|
+
return String(val);
|
|
255515
|
+
}
|
|
255516
|
+
function evalCond(expr, scope) {
|
|
255517
|
+
const e = expr.trim();
|
|
255518
|
+
const eq = e.match(/^(.+?)\s*(==|!=)\s*(.+)$/);
|
|
255519
|
+
if (eq) {
|
|
255520
|
+
const left = resolve(eq[1].trim(), scope);
|
|
255521
|
+
const rightRaw = eq[3].trim();
|
|
255522
|
+
let right;
|
|
255523
|
+
if (/^['"].*['"]$/.test(rightRaw)) right = rightRaw.replace(/^['"]|['"]$/g, "");
|
|
255524
|
+
else if (/^-?\d+$/.test(rightRaw)) right = parseInt(rightRaw, 10);
|
|
255525
|
+
else right = resolve(rightRaw, scope);
|
|
255526
|
+
if (eq[2] === "==") return left == right;
|
|
255527
|
+
return left != right;
|
|
255528
|
+
}
|
|
255529
|
+
const v = resolve(e, scope);
|
|
255530
|
+
if (Array.isArray(v)) return v.length > 0;
|
|
255531
|
+
if (typeof v === "string") return v.length > 0;
|
|
255532
|
+
return Boolean(v);
|
|
255533
|
+
}
|
|
255534
|
+
function cleanTag(inner) {
|
|
255535
|
+
return inner.replace(/^-|\s-$/g, "").trim();
|
|
255536
|
+
}
|
|
255537
|
+
function tokenize3(tpl) {
|
|
255538
|
+
const tokenRe = /(\{%[\s\S]*?%\}|\{\{[\s\S]*?\}\})/g;
|
|
255539
|
+
let lastIndex = 0;
|
|
255540
|
+
const tokens = [];
|
|
255541
|
+
let m2;
|
|
255542
|
+
while ((m2 = tokenRe.exec(tpl)) !== null) {
|
|
255543
|
+
if (m2.index > lastIndex) tokens.push({ type: "text", value: tpl.slice(lastIndex, m2.index) });
|
|
255544
|
+
const tok = m2[0];
|
|
255545
|
+
if (tok.startsWith("{%")) tokens.push({ type: "tag", value: cleanTag(tok.slice(2, -2)) });
|
|
255546
|
+
else tokens.push({ type: "expr", value: tok.slice(2, -2).trim() });
|
|
255547
|
+
lastIndex = m2.index + tok.length;
|
|
255548
|
+
}
|
|
255549
|
+
if (lastIndex < tpl.length) tokens.push({ type: "text", value: tpl.slice(lastIndex) });
|
|
255550
|
+
return tokens;
|
|
255551
|
+
}
|
|
255552
|
+
function findMatchingEnd(toks, start, open, close) {
|
|
255553
|
+
let depth = 1;
|
|
255554
|
+
for (let j = start + 1; j < toks.length; j++) {
|
|
255555
|
+
if (toks[j].type !== "tag") continue;
|
|
255556
|
+
const f = toks[j].value.split(/\s+/)[0];
|
|
255557
|
+
if (f === open) depth++;
|
|
255558
|
+
else if (f === close) {
|
|
255559
|
+
depth--;
|
|
255560
|
+
if (depth === 0) return j;
|
|
255561
|
+
}
|
|
255562
|
+
}
|
|
255563
|
+
throw new Error(`Unbalanced {% ${open} %}: missing {% ${close} %}`);
|
|
255564
|
+
}
|
|
255565
|
+
function findMatchingEndIf(toks, start) {
|
|
255566
|
+
let depth = 1;
|
|
255567
|
+
for (let j = start + 1; j < toks.length; j++) {
|
|
255568
|
+
if (toks[j].type !== "tag") continue;
|
|
255569
|
+
const f = toks[j].value.split(/\s+/)[0];
|
|
255570
|
+
if (f === "if") depth++;
|
|
255571
|
+
else if (f === "endif") {
|
|
255572
|
+
depth--;
|
|
255573
|
+
if (depth === 0) return j;
|
|
255574
|
+
}
|
|
255575
|
+
}
|
|
255576
|
+
throw new Error("Unbalanced {% if %}: missing {% endif %}");
|
|
255577
|
+
}
|
|
255578
|
+
function splitIfElse(body) {
|
|
255579
|
+
const ifBody = [];
|
|
255580
|
+
const elseBody = [];
|
|
255581
|
+
const elifs = [];
|
|
255582
|
+
let cur = ifBody;
|
|
255583
|
+
let curElif = null;
|
|
255584
|
+
let depth = 0;
|
|
255585
|
+
for (const t of body) {
|
|
255586
|
+
if (t.type === "tag") {
|
|
255587
|
+
const f = t.value.split(/\s+/)[0];
|
|
255588
|
+
if (f === "if") depth++;
|
|
255589
|
+
if (f === "endif") depth = Math.max(0, depth - 1);
|
|
255590
|
+
if (depth === 0 && f === "elif") {
|
|
255591
|
+
const cond = t.value.replace(/^elif\s+/, "");
|
|
255592
|
+
curElif = { cond, body: [] };
|
|
255593
|
+
elifs.push(curElif);
|
|
255594
|
+
cur = curElif.body;
|
|
255595
|
+
continue;
|
|
255596
|
+
}
|
|
255597
|
+
if (depth === 0 && f === "else") {
|
|
255598
|
+
cur = elseBody;
|
|
255599
|
+
continue;
|
|
255600
|
+
}
|
|
255601
|
+
}
|
|
255602
|
+
cur.push(t);
|
|
255603
|
+
}
|
|
255604
|
+
return { ifBody, elseBody, elifs };
|
|
255605
|
+
}
|
|
255606
|
+
function renderTemplate2(tpl, vars = {}) {
|
|
255607
|
+
const tokens = tokenize3(tpl);
|
|
255608
|
+
function renderTokens2(toks, scope) {
|
|
255609
|
+
let s = "";
|
|
255610
|
+
let idx = 0;
|
|
255611
|
+
while (idx < toks.length) {
|
|
255612
|
+
const t = toks[idx];
|
|
255613
|
+
if (t.type === "text") {
|
|
255614
|
+
s += t.value;
|
|
255615
|
+
idx++;
|
|
255616
|
+
continue;
|
|
255617
|
+
}
|
|
255618
|
+
if (t.type === "expr") {
|
|
255619
|
+
s += evalExpr(t.value, scope);
|
|
255620
|
+
idx++;
|
|
255621
|
+
continue;
|
|
255622
|
+
}
|
|
255623
|
+
const tag = t.value;
|
|
255624
|
+
const first = tag.split(/\s+/)[0];
|
|
255625
|
+
if (first === "for") {
|
|
255626
|
+
const fm = tag.match(/^for\s+(\w+)\s+in\s+(.+)$/);
|
|
255627
|
+
const fk = tag.match(/^for\s+(\w+)\s*,\s*(\w+)\s+in\s+(.+?)\.items\(\)$/);
|
|
255628
|
+
const bodyEnd = findMatchingEnd(toks, idx, "for", "endfor");
|
|
255629
|
+
const body = toks.slice(idx + 1, bodyEnd);
|
|
255630
|
+
idx = bodyEnd + 1;
|
|
255631
|
+
if (fk) {
|
|
255632
|
+
const kName = fk[1];
|
|
255633
|
+
const vName = fk[2];
|
|
255634
|
+
const obj = resolve(fk[3], scope);
|
|
255635
|
+
if (obj && typeof obj === "object") {
|
|
255636
|
+
for (const [k, v] of Object.entries(obj)) {
|
|
255637
|
+
s += renderTokens2(body, { ...scope, [kName]: k, [vName]: v });
|
|
255638
|
+
}
|
|
255639
|
+
}
|
|
255640
|
+
} else if (fm) {
|
|
255641
|
+
const name = fm[1];
|
|
255642
|
+
const list = resolve(fm[2], scope);
|
|
255643
|
+
if (Array.isArray(list)) {
|
|
255644
|
+
for (const item of list) s += renderTokens2(body, { ...scope, [name]: item });
|
|
255645
|
+
} else if (typeof list === "string") {
|
|
255646
|
+
for (const ch of list) s += renderTokens2(body, { ...scope, [name]: ch });
|
|
255647
|
+
} else if (list && typeof list === "object") {
|
|
255648
|
+
for (const [k, v] of Object.entries(list)) {
|
|
255649
|
+
s += renderTokens2(body, { ...scope, [name]: { key: k, value: v } });
|
|
255650
|
+
}
|
|
255651
|
+
}
|
|
255652
|
+
}
|
|
255653
|
+
continue;
|
|
255654
|
+
}
|
|
255655
|
+
if (first === "if") {
|
|
255656
|
+
const cond = tag.replace(/^if\s+/, "");
|
|
255657
|
+
const bodyEnd = findMatchingEndIf(toks, idx);
|
|
255658
|
+
const { ifBody, elseBody, elifs } = splitIfElse(toks.slice(idx + 1, bodyEnd));
|
|
255659
|
+
idx = bodyEnd + 1;
|
|
255660
|
+
let rendered = false;
|
|
255661
|
+
if (evalCond(cond, scope)) {
|
|
255662
|
+
s += renderTokens2(ifBody, scope);
|
|
255663
|
+
rendered = true;
|
|
255664
|
+
} else {
|
|
255665
|
+
for (const e of elifs) {
|
|
255666
|
+
if (evalCond(e.cond, scope)) {
|
|
255667
|
+
s += renderTokens2(e.body, scope);
|
|
255668
|
+
rendered = true;
|
|
255669
|
+
break;
|
|
255670
|
+
}
|
|
255671
|
+
}
|
|
255672
|
+
if (!rendered && elseBody.length) s += renderTokens2(elseBody, scope);
|
|
255673
|
+
}
|
|
255674
|
+
continue;
|
|
255675
|
+
}
|
|
255676
|
+
idx++;
|
|
255677
|
+
}
|
|
255678
|
+
return s;
|
|
255679
|
+
}
|
|
255680
|
+
return renderTokens2(tokens, vars);
|
|
255681
|
+
}
|
|
255682
|
+
function diffStrings(a, b) {
|
|
255683
|
+
const aLines = a.split("\n");
|
|
255684
|
+
const bLines = b.split("\n");
|
|
255685
|
+
const lines = [];
|
|
255686
|
+
const max = Math.max(aLines.length, bLines.length);
|
|
255687
|
+
for (let i = 0; i < max; i++) {
|
|
255688
|
+
const al = aLines[i];
|
|
255689
|
+
const bl = bLines[i];
|
|
255690
|
+
if (al === bl) {
|
|
255691
|
+
if (al !== void 0) lines.push(` ${al}`);
|
|
255692
|
+
} else {
|
|
255693
|
+
if (al !== void 0) lines.push(`- ${al}`);
|
|
255694
|
+
if (bl !== void 0) lines.push(`+ ${bl}`);
|
|
255695
|
+
}
|
|
255696
|
+
}
|
|
255697
|
+
return lines.join("\n");
|
|
255698
|
+
}
|
|
255699
|
+
var init_templateEngine = __esm({
|
|
255700
|
+
"../../packages/backend/src/services/automation/templateEngine.ts"() {
|
|
255701
|
+
"use strict";
|
|
255702
|
+
}
|
|
255703
|
+
});
|
|
255704
|
+
|
|
255455
255705
|
// ../../packages/backend/src/services/automation/schedulerService.ts
|
|
255456
255706
|
var schedulerService_exports = {};
|
|
255457
255707
|
__export(schedulerService_exports, {
|
|
@@ -256465,14 +256715,18 @@ var init_scheduledTaskRunner = __esm({
|
|
|
256465
256715
|
// ../../packages/backend/src/services/automation/playbookRunner.ts
|
|
256466
256716
|
var playbookRunner_exports = {};
|
|
256467
256717
|
__export(playbookRunner_exports, {
|
|
256718
|
+
buildEnvPrefix: () => buildEnvPrefix,
|
|
256719
|
+
buildPlaybookDryRunPlan: () => buildPlaybookDryRunPlan,
|
|
256468
256720
|
clearPlaybookRuns: () => clearPlaybookRuns,
|
|
256721
|
+
evaluateWhen: () => evaluateWhen,
|
|
256469
256722
|
executePlaybook: () => executePlaybook,
|
|
256470
256723
|
getPlaybookRun: () => getPlaybookRun,
|
|
256471
256724
|
listPlaybookRuns: () => listPlaybookRuns,
|
|
256472
256725
|
matchExpectation: () => matchExpectation,
|
|
256473
256726
|
resolvePlaybookStepCommand: () => resolvePlaybookStepCommand,
|
|
256474
256727
|
resolveRollbackCommand: () => resolveRollbackCommand,
|
|
256475
|
-
resolveValidationCommand: () => resolveValidationCommand
|
|
256728
|
+
resolveValidationCommand: () => resolveValidationCommand,
|
|
256729
|
+
runCommandWithTimeout: () => runCommandWithTimeout
|
|
256476
256730
|
});
|
|
256477
256731
|
function listPlaybookRuns() {
|
|
256478
256732
|
return runHistory;
|
|
@@ -256485,6 +256739,9 @@ function clearPlaybookRuns() {
|
|
|
256485
256739
|
}
|
|
256486
256740
|
function resolvePlaybookStepCommand(step, automationManager) {
|
|
256487
256741
|
if (step.kind === "wait") return null;
|
|
256742
|
+
if (step.kind === "template") return null;
|
|
256743
|
+
if (step.kind === "playbook") return null;
|
|
256744
|
+
if (step.kind === "healthCheck") return null;
|
|
256488
256745
|
if (step.kind === "command") {
|
|
256489
256746
|
const cmd2 = (step.command ?? "").trim();
|
|
256490
256747
|
if (!cmd2) throw new Error(`Step "${step.name ?? step.id}": empty command`);
|
|
@@ -256498,6 +256755,96 @@ function resolvePlaybookStepCommand(step, automationManager) {
|
|
|
256498
256755
|
if (!cmd) throw new Error(`Step "${step.name ?? step.id}" script "${script.name}" has an empty command`);
|
|
256499
256756
|
return cmd;
|
|
256500
256757
|
}
|
|
256758
|
+
async function runCommandWithTimeout(deps, terminalId, command, timeoutSeconds) {
|
|
256759
|
+
if (!timeoutSeconds || timeoutSeconds <= 0) {
|
|
256760
|
+
const r = await deps.terminalService.runCommandAndWait(terminalId, command);
|
|
256761
|
+
return { exitCode: r.exitCode, stdoutDelta: r.stdoutDelta ?? "", timedOut: false };
|
|
256762
|
+
}
|
|
256763
|
+
return await new Promise((resolve2) => {
|
|
256764
|
+
let settled = false;
|
|
256765
|
+
const settle = (v) => {
|
|
256766
|
+
if (settled) return;
|
|
256767
|
+
settled = true;
|
|
256768
|
+
clearTimeout(timer);
|
|
256769
|
+
resolve2(v);
|
|
256770
|
+
};
|
|
256771
|
+
const timer = setTimeout(() => {
|
|
256772
|
+
settle({ exitCode: -1, stdoutDelta: "", timedOut: true });
|
|
256773
|
+
}, timeoutSeconds * 1e3);
|
|
256774
|
+
deps.terminalService.runCommandAndWait(terminalId, command).then((r) => settle({ exitCode: r.exitCode, stdoutDelta: r.stdoutDelta ?? "", timedOut: false })).catch(() => settle({ exitCode: -1, stdoutDelta: "", timedOut: false }));
|
|
256775
|
+
});
|
|
256776
|
+
}
|
|
256777
|
+
function evaluateWhen(when, paramValues) {
|
|
256778
|
+
const exprMatch = when.match(/^\{\{\s*([\w.]+)\s*\}\}\s*(==|!=)\s*(.+)$/);
|
|
256779
|
+
if (exprMatch) {
|
|
256780
|
+
const [, varName, op, rawValue2] = exprMatch;
|
|
256781
|
+
const actual = paramValues[varName] ?? "";
|
|
256782
|
+
const expected = rawValue2.trim().replace(/^['"]|['"]$/g, "");
|
|
256783
|
+
return { mode: "expression", result: op === "==" ? actual === expected : actual !== expected };
|
|
256784
|
+
}
|
|
256785
|
+
return { mode: "command", result: false, command: when };
|
|
256786
|
+
}
|
|
256787
|
+
function buildEnvPrefix(env, paramValues) {
|
|
256788
|
+
if (!env || Object.keys(env).length === 0) return "";
|
|
256789
|
+
const exports2 = Object.entries(env).map(([k, v]) => {
|
|
256790
|
+
const value = v.replace(/\{\{\s*([\w.]+)\s*\}\}/g, (_, name) => paramValues[name] ?? "");
|
|
256791
|
+
const safe = value.replace(/'/g, "'\\''");
|
|
256792
|
+
return `export ${k}='${safe}'`;
|
|
256793
|
+
});
|
|
256794
|
+
return exports2.join(" && ") + " && ";
|
|
256795
|
+
}
|
|
256796
|
+
function buildPlaybookDryRunPlan(deps, playbook) {
|
|
256797
|
+
const settings = deps.getSettings();
|
|
256798
|
+
const scope = { groupId: playbook.groupId, tags: playbook.tags, targets: playbook.targets };
|
|
256799
|
+
const targets = resolveScheduledTaskTargets(scope, settings);
|
|
256800
|
+
const notes = [];
|
|
256801
|
+
const steps = [];
|
|
256802
|
+
playbook.steps.forEach((step, i) => {
|
|
256803
|
+
const entry = {
|
|
256804
|
+
index: i,
|
|
256805
|
+
name: step.name,
|
|
256806
|
+
kind: step.kind,
|
|
256807
|
+
timeoutSeconds: step.timeoutSeconds ?? playbook.defaultStepTimeoutSeconds,
|
|
256808
|
+
retryAttempts: step.retryAttempts ?? playbook.defaultStepRetryAttempts,
|
|
256809
|
+
when: step.when
|
|
256810
|
+
};
|
|
256811
|
+
try {
|
|
256812
|
+
if (step.kind === "wait") {
|
|
256813
|
+
entry.command = `# wait ${step.waitSeconds ?? 0}s`;
|
|
256814
|
+
} else if (step.kind === "template") {
|
|
256815
|
+
entry.command = `# render template ${step.templateId} \u2192 ${step.templateTargetPath ?? "(no path)"}`;
|
|
256816
|
+
if (!step.templateId) notes.push(`Step ${i + 1}: template step missing templateId`);
|
|
256817
|
+
if (!step.templateTargetPath) notes.push(`Step ${i + 1}: template step missing templateTargetPath`);
|
|
256818
|
+
} else if (step.kind === "playbook") {
|
|
256819
|
+
entry.command = `# sub-playbook ${step.playbookId}`;
|
|
256820
|
+
if (!step.playbookId) notes.push(`Step ${i + 1}: playbook step missing playbookId`);
|
|
256821
|
+
} else if (step.kind === "healthCheck") {
|
|
256822
|
+
entry.command = `# healthCheck ${step.healthUrl} expect ${step.healthExpectStatus ?? 200}`;
|
|
256823
|
+
if (!step.healthUrl) notes.push(`Step ${i + 1}: healthCheck step missing healthUrl`);
|
|
256824
|
+
} else {
|
|
256825
|
+
const cmd = resolvePlaybookStepCommand(step, deps.automationManager);
|
|
256826
|
+
entry.command = cmd ?? void 0;
|
|
256827
|
+
}
|
|
256828
|
+
} catch (e) {
|
|
256829
|
+
entry.command = `# ERROR: ${e instanceof Error ? e.message : String(e)}`;
|
|
256830
|
+
notes.push(`Step ${i + 1}: ${e instanceof Error ? e.message : String(e)}`);
|
|
256831
|
+
}
|
|
256832
|
+
steps.push(entry);
|
|
256833
|
+
});
|
|
256834
|
+
if (playbook.maxParallelTargets && playbook.maxParallelTargets > 1) {
|
|
256835
|
+
notes.push(`Targets run in parallel (max ${playbook.maxParallelTargets}).`);
|
|
256836
|
+
}
|
|
256837
|
+
if (playbook.maxRuntimeMinutes) {
|
|
256838
|
+
notes.push(`Circuit breaker: aborts after ${playbook.maxRuntimeMinutes} minutes.`);
|
|
256839
|
+
}
|
|
256840
|
+
return {
|
|
256841
|
+
playbookId: playbook.id,
|
|
256842
|
+
playbookName: playbook.name,
|
|
256843
|
+
targets: targets.length === 0 ? [{ name: "local", kind: "local" }] : targets.map((t) => ({ name: t.name, kind: t.kind })),
|
|
256844
|
+
steps,
|
|
256845
|
+
notes
|
|
256846
|
+
};
|
|
256847
|
+
}
|
|
256501
256848
|
function resolveValidationCommand(validate7, automationManager) {
|
|
256502
256849
|
if ((validate7.command ?? "").trim()) return validate7.command.trim();
|
|
256503
256850
|
const script = automationManager.listScripts().find((s) => s.id === validate7.scriptId);
|
|
@@ -256634,13 +256981,111 @@ async function executePlaybook(deps, playbook) {
|
|
|
256634
256981
|
await sleep3(Math.max(0, (step.waitSeconds ?? 0) * 1e3));
|
|
256635
256982
|
continue;
|
|
256636
256983
|
}
|
|
256637
|
-
|
|
256984
|
+
if (step.when) {
|
|
256985
|
+
const cond = evaluateWhen(step.when, {});
|
|
256986
|
+
if (cond.mode === "expression") {
|
|
256987
|
+
if (!cond.result) {
|
|
256988
|
+
log(`[playbook] "${playbook.name}" @ ${name}: step ${i + 1} skipped (when: ${step.when})`);
|
|
256989
|
+
stepOutcome.output = `# skipped (when: ${step.when})`;
|
|
256990
|
+
continue;
|
|
256991
|
+
}
|
|
256992
|
+
} else if (cond.command) {
|
|
256993
|
+
const check2 = await deps.terminalService.runCommandAndWait(terminalId, cond.command);
|
|
256994
|
+
if (check2.exitCode !== 0 && check2.exitCode !== void 0) {
|
|
256995
|
+
log(`[playbook] "${playbook.name}" @ ${name}: step ${i + 1} skipped (when-check exit ${check2.exitCode})`);
|
|
256996
|
+
stepOutcome.output = `# skipped (when-check exit ${check2.exitCode})`;
|
|
256997
|
+
continue;
|
|
256998
|
+
}
|
|
256999
|
+
}
|
|
257000
|
+
}
|
|
257001
|
+
if (step.kind === "template") {
|
|
257002
|
+
const template = deps.automationManager.listTemplates().find((t) => t.id === step.templateId);
|
|
257003
|
+
if (!template) throw new Error(`template step references missing template "${step.templateId}"`);
|
|
257004
|
+
if (!step.templateTargetPath) throw new Error("template step missing templateTargetPath");
|
|
257005
|
+
const { renderTemplate: renderTemplate3 } = await Promise.resolve().then(() => (init_templateEngine(), templateEngine_exports));
|
|
257006
|
+
const rendered = renderTemplate3(template.body, step.templateValues ?? {});
|
|
257007
|
+
const encoded = Buffer.from(rendered, "utf8").toString("base64");
|
|
257008
|
+
const writeCmd = `echo '${encoded}' | base64 -d > ${step.templateTargetPath}`;
|
|
257009
|
+
log(`[playbook] "${playbook.name}" @ ${name}: step ${i + 1} (template \u2192 ${step.templateTargetPath})`);
|
|
257010
|
+
const result2 = await runCommandWithTimeout(deps, terminalId, writeCmd, step.timeoutSeconds ?? playbook.defaultStepTimeoutSeconds);
|
|
257011
|
+
stepOutcome.exitCode = result2.exitCode;
|
|
257012
|
+
stepOutcome.output = `# wrote ${rendered.length} bytes to ${step.templateTargetPath}`;
|
|
257013
|
+
stepOutcome.ok = result2.exitCode === 0 || result2.exitCode === void 0;
|
|
257014
|
+
if (!stepOutcome.ok) stepOutcome.error = result2.timedOut ? "template write timed out" : `exit code ${result2.exitCode}`;
|
|
257015
|
+
recordLedger(name, stepOutcome, "execute", stepOutcome.ok, stepOutcome.output);
|
|
257016
|
+
if (!stepOutcome.ok) throw new Error(stepOutcome.error);
|
|
257017
|
+
continue;
|
|
257018
|
+
}
|
|
257019
|
+
if (step.kind === "healthCheck") {
|
|
257020
|
+
if (!step.healthUrl) throw new Error("healthCheck step missing healthUrl");
|
|
257021
|
+
const expectStatus = step.healthExpectStatus ?? 200;
|
|
257022
|
+
const timeoutSec2 = step.healthTimeoutSeconds ?? 60;
|
|
257023
|
+
const intervalSec = step.healthIntervalSeconds ?? 5;
|
|
257024
|
+
const deadline2 = Date.now() + timeoutSec2 * 1e3;
|
|
257025
|
+
log(`[playbook] "${playbook.name}" @ ${name}: step ${i + 1} (healthCheck ${step.healthUrl})`);
|
|
257026
|
+
let healthy = false;
|
|
257027
|
+
let lastStatus = 0;
|
|
257028
|
+
while (Date.now() < deadline2) {
|
|
257029
|
+
const checkCmd = `curl -s -o /dev/null -w '%{http_code}' ${step.healthUrl.replace(/'/g, "'\\''")}`;
|
|
257030
|
+
const r = await deps.terminalService.runCommandAndWait(terminalId, checkCmd);
|
|
257031
|
+
lastStatus = Number((r.stdoutDelta ?? "").trim()) || 0;
|
|
257032
|
+
if (lastStatus === expectStatus) {
|
|
257033
|
+
healthy = true;
|
|
257034
|
+
break;
|
|
257035
|
+
}
|
|
257036
|
+
await sleep3(intervalSec * 1e3);
|
|
257037
|
+
}
|
|
257038
|
+
stepOutcome.ok = healthy;
|
|
257039
|
+
stepOutcome.output = `# healthCheck ${step.healthUrl} \u2192 HTTP ${lastStatus} (expected ${expectStatus})`;
|
|
257040
|
+
if (!healthy) stepOutcome.error = `healthCheck failed: HTTP ${lastStatus} != ${expectStatus} after ${timeoutSec2}s`;
|
|
257041
|
+
recordLedger(name, stepOutcome, "execute", healthy, stepOutcome.output);
|
|
257042
|
+
if (!healthy) throw new Error(stepOutcome.error);
|
|
257043
|
+
continue;
|
|
257044
|
+
}
|
|
257045
|
+
if (step.kind === "playbook") {
|
|
257046
|
+
if (!step.playbookId) throw new Error("playbook step missing playbookId");
|
|
257047
|
+
const sub = deps.automationManager.listPlaybooks().find((p) => p.id === step.playbookId || p.name === step.playbookId);
|
|
257048
|
+
if (!sub) throw new Error(`playbook step references missing playbook "${step.playbookId}"`);
|
|
257049
|
+
log(`[playbook] "${playbook.name}" @ ${name}: step ${i + 1} (sub-playbook "${sub.name}")`);
|
|
257050
|
+
const subRecord = await executePlaybook(
|
|
257051
|
+
{ ...deps, onLog: log },
|
|
257052
|
+
{ ...sub, targets: playbook.targets, tags: playbook.tags, groupId: playbook.groupId }
|
|
257053
|
+
);
|
|
257054
|
+
stepOutcome.ok = subRecord.ok;
|
|
257055
|
+
stepOutcome.output = `# sub-playbook "${sub.name}" ${subRecord.ok ? "ok" : "FAILED"} on ${subRecord.targets.length} target(s)`;
|
|
257056
|
+
if (!subRecord.ok) {
|
|
257057
|
+
const failed = subRecord.targets.filter((t) => !t.ok).map((t) => t.target);
|
|
257058
|
+
stepOutcome.error = `sub-playbook "${sub.name}" failed on: ${failed.join(", ")}`;
|
|
257059
|
+
}
|
|
257060
|
+
recordLedger(name, stepOutcome, "execute", stepOutcome.ok, stepOutcome.output);
|
|
257061
|
+
if (!stepOutcome.ok) throw new Error(stepOutcome.error);
|
|
257062
|
+
continue;
|
|
257063
|
+
}
|
|
257064
|
+
const rawCommand = resolvePlaybookStepCommand(step, deps.automationManager);
|
|
257065
|
+
const command = buildEnvPrefix(step.env, {}) + rawCommand;
|
|
256638
257066
|
log(`[playbook] "${playbook.name}" @ ${name}: step ${i + 1}/${playbook.steps.length}${step.name ? ` (${step.name})` : ""}`);
|
|
256639
|
-
const
|
|
256640
|
-
|
|
256641
|
-
|
|
256642
|
-
|
|
256643
|
-
|
|
257067
|
+
const maxAttempts = 1 + Math.max(0, step.retryAttempts ?? playbook.defaultStepRetryAttempts ?? 0);
|
|
257068
|
+
const retryDelay = Math.max(0, step.retryDelaySeconds ?? 30);
|
|
257069
|
+
const timeoutSec = step.timeoutSeconds ?? playbook.defaultStepTimeoutSeconds;
|
|
257070
|
+
let attempt = 0;
|
|
257071
|
+
let result = null;
|
|
257072
|
+
while (attempt < maxAttempts) {
|
|
257073
|
+
attempt++;
|
|
257074
|
+
result = await runCommandWithTimeout(deps, terminalId, command, timeoutSec);
|
|
257075
|
+
const ok = result.exitCode === 0 || result.exitCode === void 0;
|
|
257076
|
+
if (ok) break;
|
|
257077
|
+
if (attempt < maxAttempts) {
|
|
257078
|
+
log(`[playbook] "${playbook.name}" @ ${name}: step ${i + 1} attempt ${attempt}/${maxAttempts} failed (${result.timedOut ? "timeout" : `exit ${result.exitCode}`}) \u2014 retrying in ${retryDelay}s`);
|
|
257079
|
+
await sleep3(retryDelay * 1e3);
|
|
257080
|
+
}
|
|
257081
|
+
}
|
|
257082
|
+
const finalResult = result;
|
|
257083
|
+
stepOutcome.exitCode = finalResult.exitCode;
|
|
257084
|
+
stepOutcome.output = step.outputCapture === "full" ? finalResult.stdoutDelta : tail3(finalResult.stdoutDelta);
|
|
257085
|
+
stepOutcome.ok = finalResult.exitCode === 0 || finalResult.exitCode === void 0;
|
|
257086
|
+
if (!stepOutcome.ok) {
|
|
257087
|
+
stepOutcome.error = finalResult.timedOut ? `timed out after ${timeoutSec}s` : `exit code ${finalResult.exitCode}`;
|
|
257088
|
+
}
|
|
256644
257089
|
recordLedger(name, stepOutcome, "execute", stepOutcome.ok, stepOutcome.ok ? stepOutcome.output : stepOutcome.error);
|
|
256645
257090
|
if (stepOutcome.ok && step.validate) {
|
|
256646
257091
|
try {
|
|
@@ -256708,10 +257153,43 @@ async function executePlaybook(deps, playbook) {
|
|
|
256708
257153
|
})
|
|
256709
257154
|
);
|
|
256710
257155
|
} else {
|
|
256711
|
-
|
|
257156
|
+
const maxParallel = Math.max(1, playbook.maxParallelTargets ?? 1);
|
|
257157
|
+
const onTargetError = playbook.onTargetError ?? "stop";
|
|
257158
|
+
const runtimeDeadline = playbook.maxRuntimeMinutes ? Date.now() + playbook.maxRuntimeMinutes * 6e4 : Number.POSITIVE_INFINITY;
|
|
257159
|
+
const configs = targets.map((target) => {
|
|
256712
257160
|
log(`[playbook] "${playbook.name}" \u2192 ${target.kind}://${target.name}`);
|
|
256713
257161
|
const config2 = target.kind === "ssh" ? sshEntryToConfig(target.ssh, settings) : target.kind === "winrm" ? winrmEntryToConfig(target.winrm) : serialEntryToConfig(target.serial);
|
|
256714
|
-
|
|
257162
|
+
return { name: target.name, config: config2 };
|
|
257163
|
+
});
|
|
257164
|
+
if (maxParallel <= 1) {
|
|
257165
|
+
for (const { name, config: config2 } of configs) {
|
|
257166
|
+
if (Date.now() > runtimeDeadline) {
|
|
257167
|
+
log(`[playbook] "${playbook.name}": maxRuntimeMinutes exceeded \u2014 aborting remaining targets`);
|
|
257168
|
+
record2.targets.push({ target: name, ok: false, steps: [], error: "playbook runtime limit exceeded" });
|
|
257169
|
+
if (onTargetError === "stop") break;
|
|
257170
|
+
continue;
|
|
257171
|
+
}
|
|
257172
|
+
const t = await runTarget(name, config2);
|
|
257173
|
+
record2.targets.push(t);
|
|
257174
|
+
if (!t.ok && onTargetError === "stop") {
|
|
257175
|
+
log(`[playbook] "${playbook.name}": target ${name} failed \u2014 stopping remaining targets (onTargetError=stop)`);
|
|
257176
|
+
break;
|
|
257177
|
+
}
|
|
257178
|
+
}
|
|
257179
|
+
} else {
|
|
257180
|
+
for (let i = 0; i < configs.length; i += maxParallel) {
|
|
257181
|
+
if (Date.now() > runtimeDeadline) {
|
|
257182
|
+
log(`[playbook] "${playbook.name}": maxRuntimeMinutes exceeded \u2014 aborting remaining targets`);
|
|
257183
|
+
break;
|
|
257184
|
+
}
|
|
257185
|
+
const batch = configs.slice(i, i + maxParallel);
|
|
257186
|
+
const results = await Promise.all(batch.map(({ name, config: config2 }) => runTarget(name, config2)));
|
|
257187
|
+
results.forEach((t) => record2.targets.push(t));
|
|
257188
|
+
if (onTargetError === "stop" && results.some((t) => !t.ok)) {
|
|
257189
|
+
log(`[playbook] "${playbook.name}": a target in this batch failed \u2014 stopping remaining targets (onTargetError=stop)`);
|
|
257190
|
+
break;
|
|
257191
|
+
}
|
|
257192
|
+
}
|
|
256715
257193
|
}
|
|
256716
257194
|
}
|
|
256717
257195
|
record2.ok = record2.targets.every((t) => t.ok);
|
|
@@ -256910,6 +257388,34 @@ async function runPlaybook(args, context2) {
|
|
|
256910
257388
|
emit6(context2, "run_playbook", args, msg2);
|
|
256911
257389
|
return msg2;
|
|
256912
257390
|
}
|
|
257391
|
+
if (args.dryRun) {
|
|
257392
|
+
const { buildPlaybookDryRunPlan: buildPlaybookDryRunPlan2 } = await Promise.resolve().then(() => (init_playbookRunner(), playbookRunner_exports));
|
|
257393
|
+
const plan = buildPlaybookDryRunPlan2(
|
|
257394
|
+
{ automationManager: m2, getSettings: () => settings },
|
|
257395
|
+
playbook
|
|
257396
|
+
);
|
|
257397
|
+
const targetList = plan.targets.map((t) => `${t.kind}://${t.name}`).join(", ");
|
|
257398
|
+
const stepList = plan.steps.map((s) => {
|
|
257399
|
+
const bits = [
|
|
257400
|
+
s.command ?? "(no command)",
|
|
257401
|
+
s.timeoutSeconds ? `timeout=${s.timeoutSeconds}s` : "",
|
|
257402
|
+
s.retryAttempts ? `retry=${s.retryAttempts}` : "",
|
|
257403
|
+
s.when ? `when=${s.when}` : ""
|
|
257404
|
+
].filter(Boolean).join(" ");
|
|
257405
|
+
return ` ${s.index + 1}. [${s.kind}]${s.name ? ` ${s.name}:` : ""} ${bits}`;
|
|
257406
|
+
}).join("\n");
|
|
257407
|
+
const notes = plan.notes.length ? `
|
|
257408
|
+
Notes:
|
|
257409
|
+
${plan.notes.map((n2) => ` \u26A0 ${n2}`).join("\n")}` : "";
|
|
257410
|
+
const msg2 = `Dry-run plan for "${playbook.name}" (NOTHING EXECUTED):
|
|
257411
|
+
|
|
257412
|
+
Targets (${plan.targets.length}): ${targetList}
|
|
257413
|
+
|
|
257414
|
+
Steps (${plan.steps.length}):
|
|
257415
|
+
${stepList}${notes}`;
|
|
257416
|
+
emit6(context2, "run_playbook", args, msg2);
|
|
257417
|
+
return msg2;
|
|
257418
|
+
}
|
|
256913
257419
|
const settings = {
|
|
256914
257420
|
connections: {
|
|
256915
257421
|
ssh: context2.savedSshConnections ?? [],
|
|
@@ -257007,7 +257513,8 @@ var init_playbook_tools = __esm({
|
|
|
257007
257513
|
runPlaybookSchema = external_exports.object({
|
|
257008
257514
|
id: external_exports.string().optional().describe("Playbook id to run."),
|
|
257009
257515
|
name: external_exports.string().optional().describe("Playbook name to run (when id is not given)."),
|
|
257010
|
-
paramValues: external_exports.record(external_exports.string(), external_exports.string()).optional().describe("Run-time values for the playbook's declared params (Advanced Automation).")
|
|
257516
|
+
paramValues: external_exports.record(external_exports.string(), external_exports.string()).optional().describe("Run-time values for the playbook's declared params (Advanced Automation)."),
|
|
257517
|
+
dryRun: external_exports.boolean().optional().describe("v3.2.17: resolve targets + commands and return the plan WITHOUT executing anything. Use before a MOP change to preview what will happen.")
|
|
257011
257518
|
});
|
|
257012
257519
|
}
|
|
257013
257520
|
});
|
|
@@ -352676,246 +353183,7 @@ ${truncateForFleet(banner, 1500)}
|
|
|
352676
353183
|
|
|
352677
353184
|
// ../../packages/backend/src/services/AgentHelper/tools/automation_tools.ts
|
|
352678
353185
|
init_zod();
|
|
352679
|
-
|
|
352680
|
-
// ../../packages/backend/src/services/automation/templateEngine.ts
|
|
352681
|
-
function resolve(expr, scope) {
|
|
352682
|
-
const parts = expr.trim().split(".");
|
|
352683
|
-
let cur = scope;
|
|
352684
|
-
for (const p of parts) {
|
|
352685
|
-
if (cur == null) return void 0;
|
|
352686
|
-
if (typeof cur === "object") {
|
|
352687
|
-
cur = cur[p];
|
|
352688
|
-
} else {
|
|
352689
|
-
return void 0;
|
|
352690
|
-
}
|
|
352691
|
-
}
|
|
352692
|
-
return cur;
|
|
352693
|
-
}
|
|
352694
|
-
function applyFilters(value, filters, scope) {
|
|
352695
|
-
let v = value;
|
|
352696
|
-
for (const f of filters) {
|
|
352697
|
-
const m2 = f.match(/^(\w+)\s*(?:\((.*)\))?$/);
|
|
352698
|
-
if (!m2) continue;
|
|
352699
|
-
const name = m2[1];
|
|
352700
|
-
const argRaw = m2[2] ?? "";
|
|
352701
|
-
const arg = argRaw.replace(/^['"]|['"]$/g, "");
|
|
352702
|
-
switch (name) {
|
|
352703
|
-
case "default":
|
|
352704
|
-
if (v === void 0 || v === null || v === "") {
|
|
352705
|
-
v = arg in scope ? resolve(arg, scope) : arg;
|
|
352706
|
-
}
|
|
352707
|
-
break;
|
|
352708
|
-
case "upper":
|
|
352709
|
-
v = String(v ?? "").toUpperCase();
|
|
352710
|
-
break;
|
|
352711
|
-
case "lower":
|
|
352712
|
-
v = String(v ?? "").toLowerCase();
|
|
352713
|
-
break;
|
|
352714
|
-
case "length":
|
|
352715
|
-
if (Array.isArray(v)) v = v.length;
|
|
352716
|
-
else if (typeof v === "string") v = v.length;
|
|
352717
|
-
else v = 0;
|
|
352718
|
-
break;
|
|
352719
|
-
default:
|
|
352720
|
-
break;
|
|
352721
|
-
}
|
|
352722
|
-
}
|
|
352723
|
-
return v;
|
|
352724
|
-
}
|
|
352725
|
-
function evalExpr(expr, scope) {
|
|
352726
|
-
const parts = expr.split("|").map((p) => p.trim());
|
|
352727
|
-
const varExpr = parts[0];
|
|
352728
|
-
const filters = parts.slice(1);
|
|
352729
|
-
let val = resolve(varExpr, scope);
|
|
352730
|
-
val = applyFilters(val, filters, scope);
|
|
352731
|
-
if (val === void 0 || val === null) return "";
|
|
352732
|
-
if (Array.isArray(val)) return val.join(", ");
|
|
352733
|
-
if (typeof val === "object") return JSON.stringify(val);
|
|
352734
|
-
return String(val);
|
|
352735
|
-
}
|
|
352736
|
-
function evalCond(expr, scope) {
|
|
352737
|
-
const e = expr.trim();
|
|
352738
|
-
const eq = e.match(/^(.+?)\s*(==|!=)\s*(.+)$/);
|
|
352739
|
-
if (eq) {
|
|
352740
|
-
const left = resolve(eq[1].trim(), scope);
|
|
352741
|
-
const rightRaw = eq[3].trim();
|
|
352742
|
-
let right;
|
|
352743
|
-
if (/^['"].*['"]$/.test(rightRaw)) right = rightRaw.replace(/^['"]|['"]$/g, "");
|
|
352744
|
-
else if (/^-?\d+$/.test(rightRaw)) right = parseInt(rightRaw, 10);
|
|
352745
|
-
else right = resolve(rightRaw, scope);
|
|
352746
|
-
if (eq[2] === "==") return left == right;
|
|
352747
|
-
return left != right;
|
|
352748
|
-
}
|
|
352749
|
-
const v = resolve(e, scope);
|
|
352750
|
-
if (Array.isArray(v)) return v.length > 0;
|
|
352751
|
-
if (typeof v === "string") return v.length > 0;
|
|
352752
|
-
return Boolean(v);
|
|
352753
|
-
}
|
|
352754
|
-
function cleanTag(inner) {
|
|
352755
|
-
return inner.replace(/^-|\s-$/g, "").trim();
|
|
352756
|
-
}
|
|
352757
|
-
function tokenize3(tpl) {
|
|
352758
|
-
const tokenRe = /(\{%[\s\S]*?%\}|\{\{[\s\S]*?\}\})/g;
|
|
352759
|
-
let lastIndex = 0;
|
|
352760
|
-
const tokens = [];
|
|
352761
|
-
let m2;
|
|
352762
|
-
while ((m2 = tokenRe.exec(tpl)) !== null) {
|
|
352763
|
-
if (m2.index > lastIndex) tokens.push({ type: "text", value: tpl.slice(lastIndex, m2.index) });
|
|
352764
|
-
const tok = m2[0];
|
|
352765
|
-
if (tok.startsWith("{%")) tokens.push({ type: "tag", value: cleanTag(tok.slice(2, -2)) });
|
|
352766
|
-
else tokens.push({ type: "expr", value: tok.slice(2, -2).trim() });
|
|
352767
|
-
lastIndex = m2.index + tok.length;
|
|
352768
|
-
}
|
|
352769
|
-
if (lastIndex < tpl.length) tokens.push({ type: "text", value: tpl.slice(lastIndex) });
|
|
352770
|
-
return tokens;
|
|
352771
|
-
}
|
|
352772
|
-
function findMatchingEnd(toks, start, open, close) {
|
|
352773
|
-
let depth = 1;
|
|
352774
|
-
for (let j = start + 1; j < toks.length; j++) {
|
|
352775
|
-
if (toks[j].type !== "tag") continue;
|
|
352776
|
-
const f = toks[j].value.split(/\s+/)[0];
|
|
352777
|
-
if (f === open) depth++;
|
|
352778
|
-
else if (f === close) {
|
|
352779
|
-
depth--;
|
|
352780
|
-
if (depth === 0) return j;
|
|
352781
|
-
}
|
|
352782
|
-
}
|
|
352783
|
-
throw new Error(`Unbalanced {% ${open} %}: missing {% ${close} %}`);
|
|
352784
|
-
}
|
|
352785
|
-
function findMatchingEndIf(toks, start) {
|
|
352786
|
-
let depth = 1;
|
|
352787
|
-
for (let j = start + 1; j < toks.length; j++) {
|
|
352788
|
-
if (toks[j].type !== "tag") continue;
|
|
352789
|
-
const f = toks[j].value.split(/\s+/)[0];
|
|
352790
|
-
if (f === "if") depth++;
|
|
352791
|
-
else if (f === "endif") {
|
|
352792
|
-
depth--;
|
|
352793
|
-
if (depth === 0) return j;
|
|
352794
|
-
}
|
|
352795
|
-
}
|
|
352796
|
-
throw new Error("Unbalanced {% if %}: missing {% endif %}");
|
|
352797
|
-
}
|
|
352798
|
-
function splitIfElse(body) {
|
|
352799
|
-
const ifBody = [];
|
|
352800
|
-
const elseBody = [];
|
|
352801
|
-
const elifs = [];
|
|
352802
|
-
let cur = ifBody;
|
|
352803
|
-
let curElif = null;
|
|
352804
|
-
let depth = 0;
|
|
352805
|
-
for (const t of body) {
|
|
352806
|
-
if (t.type === "tag") {
|
|
352807
|
-
const f = t.value.split(/\s+/)[0];
|
|
352808
|
-
if (f === "if") depth++;
|
|
352809
|
-
if (f === "endif") depth = Math.max(0, depth - 1);
|
|
352810
|
-
if (depth === 0 && f === "elif") {
|
|
352811
|
-
const cond = t.value.replace(/^elif\s+/, "");
|
|
352812
|
-
curElif = { cond, body: [] };
|
|
352813
|
-
elifs.push(curElif);
|
|
352814
|
-
cur = curElif.body;
|
|
352815
|
-
continue;
|
|
352816
|
-
}
|
|
352817
|
-
if (depth === 0 && f === "else") {
|
|
352818
|
-
cur = elseBody;
|
|
352819
|
-
continue;
|
|
352820
|
-
}
|
|
352821
|
-
}
|
|
352822
|
-
cur.push(t);
|
|
352823
|
-
}
|
|
352824
|
-
return { ifBody, elseBody, elifs };
|
|
352825
|
-
}
|
|
352826
|
-
function renderTemplate2(tpl, vars = {}) {
|
|
352827
|
-
const tokens = tokenize3(tpl);
|
|
352828
|
-
function renderTokens2(toks, scope) {
|
|
352829
|
-
let s = "";
|
|
352830
|
-
let idx = 0;
|
|
352831
|
-
while (idx < toks.length) {
|
|
352832
|
-
const t = toks[idx];
|
|
352833
|
-
if (t.type === "text") {
|
|
352834
|
-
s += t.value;
|
|
352835
|
-
idx++;
|
|
352836
|
-
continue;
|
|
352837
|
-
}
|
|
352838
|
-
if (t.type === "expr") {
|
|
352839
|
-
s += evalExpr(t.value, scope);
|
|
352840
|
-
idx++;
|
|
352841
|
-
continue;
|
|
352842
|
-
}
|
|
352843
|
-
const tag = t.value;
|
|
352844
|
-
const first = tag.split(/\s+/)[0];
|
|
352845
|
-
if (first === "for") {
|
|
352846
|
-
const fm = tag.match(/^for\s+(\w+)\s+in\s+(.+)$/);
|
|
352847
|
-
const fk = tag.match(/^for\s+(\w+)\s*,\s*(\w+)\s+in\s+(.+?)\.items\(\)$/);
|
|
352848
|
-
const bodyEnd = findMatchingEnd(toks, idx, "for", "endfor");
|
|
352849
|
-
const body = toks.slice(idx + 1, bodyEnd);
|
|
352850
|
-
idx = bodyEnd + 1;
|
|
352851
|
-
if (fk) {
|
|
352852
|
-
const kName = fk[1];
|
|
352853
|
-
const vName = fk[2];
|
|
352854
|
-
const obj = resolve(fk[3], scope);
|
|
352855
|
-
if (obj && typeof obj === "object") {
|
|
352856
|
-
for (const [k, v] of Object.entries(obj)) {
|
|
352857
|
-
s += renderTokens2(body, { ...scope, [kName]: k, [vName]: v });
|
|
352858
|
-
}
|
|
352859
|
-
}
|
|
352860
|
-
} else if (fm) {
|
|
352861
|
-
const name = fm[1];
|
|
352862
|
-
const list = resolve(fm[2], scope);
|
|
352863
|
-
if (Array.isArray(list)) {
|
|
352864
|
-
for (const item of list) s += renderTokens2(body, { ...scope, [name]: item });
|
|
352865
|
-
} else if (typeof list === "string") {
|
|
352866
|
-
for (const ch of list) s += renderTokens2(body, { ...scope, [name]: ch });
|
|
352867
|
-
} else if (list && typeof list === "object") {
|
|
352868
|
-
for (const [k, v] of Object.entries(list)) {
|
|
352869
|
-
s += renderTokens2(body, { ...scope, [name]: { key: k, value: v } });
|
|
352870
|
-
}
|
|
352871
|
-
}
|
|
352872
|
-
}
|
|
352873
|
-
continue;
|
|
352874
|
-
}
|
|
352875
|
-
if (first === "if") {
|
|
352876
|
-
const cond = tag.replace(/^if\s+/, "");
|
|
352877
|
-
const bodyEnd = findMatchingEndIf(toks, idx);
|
|
352878
|
-
const { ifBody, elseBody, elifs } = splitIfElse(toks.slice(idx + 1, bodyEnd));
|
|
352879
|
-
idx = bodyEnd + 1;
|
|
352880
|
-
let rendered = false;
|
|
352881
|
-
if (evalCond(cond, scope)) {
|
|
352882
|
-
s += renderTokens2(ifBody, scope);
|
|
352883
|
-
rendered = true;
|
|
352884
|
-
} else {
|
|
352885
|
-
for (const e of elifs) {
|
|
352886
|
-
if (evalCond(e.cond, scope)) {
|
|
352887
|
-
s += renderTokens2(e.body, scope);
|
|
352888
|
-
rendered = true;
|
|
352889
|
-
break;
|
|
352890
|
-
}
|
|
352891
|
-
}
|
|
352892
|
-
if (!rendered && elseBody.length) s += renderTokens2(elseBody, scope);
|
|
352893
|
-
}
|
|
352894
|
-
continue;
|
|
352895
|
-
}
|
|
352896
|
-
idx++;
|
|
352897
|
-
}
|
|
352898
|
-
return s;
|
|
352899
|
-
}
|
|
352900
|
-
return renderTokens2(tokens, vars);
|
|
352901
|
-
}
|
|
352902
|
-
function diffStrings(a, b) {
|
|
352903
|
-
const aLines = a.split("\n");
|
|
352904
|
-
const bLines = b.split("\n");
|
|
352905
|
-
const lines = [];
|
|
352906
|
-
const max = Math.max(aLines.length, bLines.length);
|
|
352907
|
-
for (let i = 0; i < max; i++) {
|
|
352908
|
-
const al = aLines[i];
|
|
352909
|
-
const bl = bLines[i];
|
|
352910
|
-
if (al === bl) {
|
|
352911
|
-
if (al !== void 0) lines.push(` ${al}`);
|
|
352912
|
-
} else {
|
|
352913
|
-
if (al !== void 0) lines.push(`- ${al}`);
|
|
352914
|
-
if (bl !== void 0) lines.push(`+ ${bl}`);
|
|
352915
|
-
}
|
|
352916
|
-
}
|
|
352917
|
-
return lines.join("\n");
|
|
352918
|
-
}
|
|
353186
|
+
init_templateEngine();
|
|
352919
353187
|
|
|
352920
353188
|
// ../../packages/backend/src/services/automation/puttyImport.ts
|
|
352921
353189
|
function decodeSessionName(name) {
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "neuralos",
|
|
3
|
-
"version": "3.2.
|
|
4
|
-
"description": "Headless AI-native backend for RTerm / neuralOS — v3.2.
|
|
3
|
+
"version": "3.2.18",
|
|
4
|
+
"description": "Headless AI-native backend for RTerm / neuralOS — v3.2.18: cross-session history search, model failover, sub-agent delegation, REST API layer, gateway rate limiting, settings backup/restore, idle terminal timeout. 14 plugins, 61 plugin tools, parallel tool execution, captureStatus.",
|
|
5
5
|
"main": "bin/gybackend.cjs",
|
|
6
6
|
"bin": { "gybackend": "bin/gybackend.cjs" },
|
|
7
7
|
"license": "MIT",
|