zelari-code 1.6.0 → 1.7.1

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.
@@ -16576,6 +16576,28 @@ var init_filesystem = __esm({
16576
16576
  // packages/core/dist/core/tools/builtin/shellResolver.js
16577
16577
  import { existsSync as existsSync4 } from "node:fs";
16578
16578
  import { spawnSync } from "node:child_process";
16579
+ function isWslBashPath(p3) {
16580
+ if (!p3 || typeof p3 !== "string")
16581
+ return false;
16582
+ const n = p3.replace(/\//g, "\\").toLowerCase();
16583
+ if (n.includes("\\windows\\system32\\bash.exe"))
16584
+ return true;
16585
+ if (n.includes("\\windows\\syswow64\\bash.exe"))
16586
+ return true;
16587
+ if (n.includes("\\windowsapps\\bash.exe"))
16588
+ return true;
16589
+ return false;
16590
+ }
16591
+ function acceptBashPath(p3) {
16592
+ if (!p3 || p3.trim().length === 0)
16593
+ return null;
16594
+ const trimmed = p3.trim();
16595
+ if (isWslBashPath(trimmed))
16596
+ return null;
16597
+ if (!existsSyncSafe(trimmed))
16598
+ return null;
16599
+ return trimmed;
16600
+ }
16579
16601
  function resolveShell(forceReResolve = false) {
16580
16602
  if (memoized && !forceReResolve)
16581
16603
  return memoized;
@@ -16596,24 +16618,25 @@ function resolveShell(forceReResolve = false) {
16596
16618
  return memoized;
16597
16619
  }
16598
16620
  function resolveBashWindows() {
16599
- const envShell = process.env.ZELARI_SHELL;
16600
- if (envShell && envShell.trim().length > 0 && existsSyncSafe(envShell)) {
16601
- return envShell;
16602
- }
16603
- const sessionShell = process.env.SHELL;
16604
- if (sessionShell && sessionShell.trim().length > 0 && existsSyncSafe(sessionShell)) {
16605
- return sessionShell;
16606
- }
16621
+ const fromEnv = acceptBashPath(process.env.ZELARI_SHELL);
16622
+ if (fromEnv)
16623
+ return fromEnv;
16624
+ const fromSession = acceptBashPath(process.env.SHELL);
16625
+ if (fromSession)
16626
+ return fromSession;
16607
16627
  for (const p3 of STANDARD_BASH_PATHS) {
16608
- if (existsSyncSafe(p3))
16609
- return p3;
16628
+ const accepted = acceptBashPath(p3);
16629
+ if (accepted)
16630
+ return accepted;
16610
16631
  }
16611
16632
  try {
16612
16633
  const result = spawnSync("where", ["bash"], { encoding: "utf-8", windowsHide: true });
16613
16634
  if (result.status === 0 && result.stdout) {
16614
- const first = result.stdout.split(/\r?\n/).find((l) => l.trim().length > 0);
16615
- if (first && existsSyncSafe(first))
16616
- return first.trim();
16635
+ for (const line of result.stdout.split(/\r?\n/)) {
16636
+ const accepted = acceptBashPath(line);
16637
+ if (accepted)
16638
+ return accepted;
16639
+ }
16617
16640
  }
16618
16641
  } catch {
16619
16642
  }
@@ -16643,6 +16666,24 @@ var init_shellResolver = __esm({
16643
16666
 
16644
16667
  // packages/core/dist/core/tools/builtin/shell.js
16645
16668
  import { spawn } from "node:child_process";
16669
+ import { dirname } from "node:path";
16670
+ function withNodeDirOnPath(base) {
16671
+ const env = { ...base };
16672
+ try {
16673
+ const nodeDir = dirname(process.execPath);
16674
+ if (!nodeDir)
16675
+ return env;
16676
+ const sep = process.platform === "win32" ? ";" : ":";
16677
+ const current = env.PATH ?? env.Path ?? "";
16678
+ const parts = current.split(sep).filter((p3) => p3.length > 0);
16679
+ const has = parts.some((p3) => p3.toLowerCase() === nodeDir.toLowerCase());
16680
+ if (!has) {
16681
+ env.PATH = `${nodeDir}${sep}${current}`;
16682
+ }
16683
+ } catch {
16684
+ }
16685
+ return env;
16686
+ }
16646
16687
  var BashArgsSchema, INTERACTIVE_CANCEL_RE, INTERACTIVE_HINT, bashTool;
16647
16688
  var init_shell = __esm({
16648
16689
  "packages/core/dist/core/tools/builtin/shell.js"() {
@@ -16669,7 +16710,10 @@ var init_shell = __esm({
16669
16710
  const cwd = args.cwd ?? ctx.cwd;
16670
16711
  const resolved = resolveShell();
16671
16712
  let child;
16672
- const baseEnv = { ...process.env, CI: process.env.CI ?? "1" };
16713
+ const baseEnv = withNodeDirOnPath({
16714
+ ...process.env,
16715
+ CI: process.env.CI ?? "1"
16716
+ });
16673
16717
  const env = resolved.isBash ? { ...baseEnv, MSYSTEM: process.env.MSYSTEM ?? "MINGW64" } : baseEnv;
16674
16718
  if (resolved.isBash) {
16675
16719
  child = spawn(resolved.shell, ["-c", args.command], {
@@ -18401,6 +18445,366 @@ var init_systemPromptBuilder = __esm({
18401
18445
  }
18402
18446
  });
18403
18447
 
18448
+ // packages/core/dist/agents/languagePolicy.js
18449
+ function isLetter(cp) {
18450
+ return cp >= 65 && cp <= 90 || // A-Z
18451
+ cp >= 97 && cp <= 122 || // a-z
18452
+ cp >= 192 && cp <= 591 || // Latin-1 supplement + Extended A/B
18453
+ cp >= 7680 && cp <= 7935 || // Latin Extended Additional
18454
+ cp >= 880 && cp <= 1023 || // Greek
18455
+ cp >= 1024 && cp <= 1279 || // Cyrillic
18456
+ cp >= 1424 && cp <= 1535 || // Hebrew
18457
+ cp >= 1536 && cp <= 1791 || // Arabic
18458
+ cp >= 2304 && cp <= 2431 || // Devanagari
18459
+ cp >= 12352 && cp <= 12543 || // Hiragana + Katakana
18460
+ cp >= 19968 && cp <= 40959 || // CJK Unified
18461
+ cp >= 44032 && cp <= 55215;
18462
+ }
18463
+ function isLatinChar(cp) {
18464
+ return cp >= 65 && cp <= 90 || // A-Z
18465
+ cp >= 97 && cp <= 122 || // a-z
18466
+ cp >= 192 && cp <= 591 || // Latin-1 supplement + Extended A/B
18467
+ cp >= 7680 && cp <= 7935;
18468
+ }
18469
+ function detectResponseLanguage(text) {
18470
+ if (!text || text.trim().length === 0)
18471
+ return "it";
18472
+ const stripped = text.replace(/```[\s\S]*?```/g, " ").replace(/`[^`]*`/g, " ").toLowerCase();
18473
+ if (stripped.trim().length === 0)
18474
+ return "it";
18475
+ let totalLetters = 0;
18476
+ let nonLatinLetters = 0;
18477
+ for (let i = 0; i < stripped.length; i++) {
18478
+ const cp = stripped.codePointAt(i);
18479
+ if (cp === void 0)
18480
+ continue;
18481
+ if (!isLetter(cp))
18482
+ continue;
18483
+ totalLetters += 1;
18484
+ if (!isLatinChar(cp))
18485
+ nonLatinLetters += 1;
18486
+ }
18487
+ const nonLatinRatio = totalLetters > 0 ? nonLatinLetters / totalLetters : 0;
18488
+ const SCRIPT_DOMINANCE_THRESHOLD = 0.3;
18489
+ if (nonLatinRatio >= SCRIPT_DOMINANCE_THRESHOLD) {
18490
+ for (const { lang, ranges } of SCRIPT_RANGES) {
18491
+ for (const [lo, hi] of ranges) {
18492
+ for (let i = 0; i < stripped.length; i++) {
18493
+ const cp = stripped.codePointAt(i);
18494
+ if (cp !== void 0 && cp >= lo && cp <= hi)
18495
+ return lang;
18496
+ }
18497
+ }
18498
+ }
18499
+ }
18500
+ const ACCENT_VOTES = [
18501
+ // Italian-specific
18502
+ { lang: "it", weight: 2, chars: "\xE0\xE8\xE9\xEC\xF2\xF3\xF9" },
18503
+ // French-only accents
18504
+ { lang: "fr", weight: 2, chars: "\xE2\xE7\xEA\xEB\xEE\xEF\xF4\xFB\u0153\xE6" },
18505
+ // German-only
18506
+ { lang: "de", weight: 3, chars: "\xE4\xF6\xFC\xDF" },
18507
+ // Spanish-only (ñ is the discriminator)
18508
+ { lang: "es", weight: 3, chars: "\xF1\xE1\xED\xF3\xFA\xBF\xA1" },
18509
+ // Portuguese-only (ã/õ are the discriminator)
18510
+ { lang: "pt", weight: 3, chars: "\xE3\xF5\xE1\xE9\xED\xF3\xFA\xE2\xEA\xF4" },
18511
+ // Dutch-only
18512
+ { lang: "nl", weight: 2, chars: "\xE1\xE8\xE9\xEB\xED\xF3\xF6\xFA\xFC" }
18513
+ ];
18514
+ const accentScores = {};
18515
+ for (const { lang, weight, chars } of ACCENT_VOTES) {
18516
+ let hits = 0;
18517
+ for (const c of chars)
18518
+ if (stripped.includes(c))
18519
+ hits += 1;
18520
+ if (hits > 0)
18521
+ accentScores[lang] = (accentScores[lang] ?? 0) + weight * hits;
18522
+ }
18523
+ const accentOrdered = Object.entries(accentScores).sort((a, b) => b[1] - a[1]).map(([l]) => l);
18524
+ if (accentOrdered.length > 0 && accentOrdered.length === 1) {
18525
+ return accentOrdered[0];
18526
+ }
18527
+ if (accentOrdered.length > 1) {
18528
+ return accentOrdered[0];
18529
+ }
18530
+ const tokens = stripped.split(/[\s,;.!?()\[\]{}<>:'"\\/]+/).filter((t) => t.length > 1);
18531
+ if (tokens.length === 0)
18532
+ return "it";
18533
+ const scores = {};
18534
+ for (const [lang, words] of Object.entries(FUNCTION_WORDS)) {
18535
+ let hits = 0;
18536
+ const tokenSet = new Set(tokens);
18537
+ for (const w of words) {
18538
+ if (tokenSet.has(w))
18539
+ hits += 1;
18540
+ }
18541
+ if (hits > 0)
18542
+ scores[lang] = hits;
18543
+ }
18544
+ const ordered = Object.entries(scores).sort((a, b) => b[1] - a[1] || (a[0] === "it" ? -1 : b[0] === "it" ? 1 : 0)).map(([l]) => l);
18545
+ if (ordered.length > 0)
18546
+ return ordered[0];
18547
+ return "it";
18548
+ }
18549
+ function resolveResponseLanguage(text, env = process.env) {
18550
+ const raw = env.ZELARI_RESPONSE_LANG?.trim().toLowerCase();
18551
+ if (raw && raw !== "auto") {
18552
+ if (raw in LANGUAGE_LABELS)
18553
+ return raw;
18554
+ const sink = globalThis.__zelariLangWarnSink ?? ((msg) => console.warn(`[zelari-code] ${msg}`));
18555
+ sink(`ZELARI_RESPONSE_LANG='${env.ZELARI_RESPONSE_LANG}' is not a supported language (known: ${Object.keys(LANGUAGE_LABELS).join(", ")}, or 'auto'). Falling back to detection.`);
18556
+ }
18557
+ return detectResponseLanguage(text);
18558
+ }
18559
+ function buildLanguageDirective(lang) {
18560
+ const label = LANGUAGE_LABELS[lang];
18561
+ return `# Response Language \u2014 ${label}
18562
+
18563
+ Reply in **${label}** for the entirety of your response, including any final synthesis, clarifying questions, and tool-call descriptions. This directive applies in all modes (single agent, council members, zelari slices):
18564
+
18565
+ - Read the user's last message in context and mirror its language. If the user's prompt is in ${label}, reply in ${label}. If it mixes languages, default to ${label}.
18566
+ - Do not switch to English (or any other language) for code, error messages, tool names, file paths, or technical terms \u2014 code is language-neutral and stays as-is.
18567
+ - If the user explicitly asks for a different language in the same turn (e.g. "reply in English"), honor that request for the rest of this turn only.
18568
+ - For clarifying questions (---QUESTION--- blocks), write the \`question\` and \`choices\` fields in ${label} so the picker UI matches the user's language.
18569
+ - For council synthesis (Lucifero) and any final-answer turn, the user-visible text is in ${label}; intermediate specialist notes that the user never sees directly can stay in their working language.`;
18570
+ }
18571
+ function buildLanguagePolicyModule(lang) {
18572
+ return {
18573
+ type: LANGUAGE_POLICY_MODULE_TYPE,
18574
+ title: `Response Language (${LANGUAGE_LABELS[lang]})`,
18575
+ priority: 5,
18576
+ content: buildLanguageDirective(lang)
18577
+ };
18578
+ }
18579
+ function buildLanguagePolicyModuleFor(userText, env = process.env) {
18580
+ return buildLanguagePolicyModule(resolveResponseLanguage(userText, env));
18581
+ }
18582
+ var LANGUAGE_LABELS, SCRIPT_RANGES, FUNCTION_WORDS, LANGUAGE_POLICY_MODULE_TYPE;
18583
+ var init_languagePolicy = __esm({
18584
+ "packages/core/dist/agents/languagePolicy.js"() {
18585
+ "use strict";
18586
+ LANGUAGE_LABELS = {
18587
+ it: "Italian",
18588
+ en: "English",
18589
+ fr: "French",
18590
+ es: "Spanish",
18591
+ de: "German",
18592
+ pt: "Portuguese",
18593
+ nl: "Dutch",
18594
+ zh: "Chinese (Simplified)",
18595
+ ja: "Japanese",
18596
+ ko: "Korean",
18597
+ ru: "Russian",
18598
+ ar: "Arabic"
18599
+ };
18600
+ SCRIPT_RANGES = [
18601
+ // v1.7.0 fix: ja/kr MUST come before zh — kanji (CJK Unified) and hangul
18602
+ // glyphs can fall in either's range when both languages are present, but
18603
+ // hiragana/katakana (ja) and hangul jamo (kr) are unique owners. A mixed
18604
+ // string like "こんにちは、関数の作り方を…" had kanji that triggered the
18605
+ // zh range first, returning 'zh' for a clearly-Japanese message.
18606
+ { lang: "ja", ranges: [[12352, 12447], [12448, 12543]] },
18607
+ { lang: "ko", ranges: [[44032, 55215], [4352, 4607]] },
18608
+ { lang: "zh", ranges: [[19968, 40959]] },
18609
+ { lang: "ar", ranges: [[1536, 1791]] },
18610
+ { lang: "ru", ranges: [[1024, 1279]] }
18611
+ ];
18612
+ FUNCTION_WORDS = {
18613
+ it: [
18614
+ "il",
18615
+ "lo",
18616
+ "la",
18617
+ "gli",
18618
+ "le",
18619
+ "un",
18620
+ "uno",
18621
+ "una",
18622
+ "di",
18623
+ "del",
18624
+ "della",
18625
+ "dei",
18626
+ "e",
18627
+ "che",
18628
+ "chi",
18629
+ "come",
18630
+ "con",
18631
+ "per",
18632
+ "non",
18633
+ "sono",
18634
+ "questo",
18635
+ "questa",
18636
+ "quello",
18637
+ "quella",
18638
+ "fare",
18639
+ "fai",
18640
+ "crea",
18641
+ "creare",
18642
+ "mostra",
18643
+ "ciao",
18644
+ "grazie",
18645
+ "bene",
18646
+ "male",
18647
+ "puoi",
18648
+ "come",
18649
+ "cosa",
18650
+ "questo"
18651
+ ],
18652
+ en: [
18653
+ "the",
18654
+ "a",
18655
+ "an",
18656
+ "of",
18657
+ "and",
18658
+ "is",
18659
+ "are",
18660
+ "was",
18661
+ "were",
18662
+ "to",
18663
+ "for",
18664
+ "with",
18665
+ "that",
18666
+ "this",
18667
+ "these",
18668
+ "those",
18669
+ "do",
18670
+ "does",
18671
+ "make",
18672
+ "create",
18673
+ "show",
18674
+ "hello",
18675
+ "help",
18676
+ "please",
18677
+ "thanks",
18678
+ "how",
18679
+ "what",
18680
+ "where",
18681
+ "why"
18682
+ ],
18683
+ fr: [
18684
+ "le",
18685
+ "la",
18686
+ "les",
18687
+ "un",
18688
+ "une",
18689
+ "des",
18690
+ "de",
18691
+ "du",
18692
+ "et",
18693
+ "est",
18694
+ "sont",
18695
+ "que",
18696
+ "qui",
18697
+ "avec",
18698
+ "pour",
18699
+ "dans",
18700
+ "pas",
18701
+ "oui",
18702
+ "non",
18703
+ "faire",
18704
+ "cr\xE9er",
18705
+ "bonjour",
18706
+ "merci",
18707
+ "bien"
18708
+ ],
18709
+ es: [
18710
+ "el",
18711
+ "la",
18712
+ "los",
18713
+ "las",
18714
+ "un",
18715
+ "una",
18716
+ "de",
18717
+ "del",
18718
+ "y",
18719
+ "es",
18720
+ "son",
18721
+ "que",
18722
+ "con",
18723
+ "por",
18724
+ "para",
18725
+ "no",
18726
+ "s\xED",
18727
+ "hacer",
18728
+ "crear",
18729
+ "mostrar",
18730
+ "hola",
18731
+ "gracias",
18732
+ "bueno"
18733
+ ],
18734
+ de: [
18735
+ "der",
18736
+ "die",
18737
+ "das",
18738
+ "den",
18739
+ "dem",
18740
+ "des",
18741
+ "und",
18742
+ "ist",
18743
+ "sind",
18744
+ "nicht",
18745
+ "mit",
18746
+ "f\xFCr",
18747
+ "auf",
18748
+ "zu",
18749
+ "ja",
18750
+ "nein",
18751
+ "machen",
18752
+ "erstellen",
18753
+ "zeigen",
18754
+ "hallo",
18755
+ "danke",
18756
+ "gut"
18757
+ ],
18758
+ pt: [
18759
+ "o",
18760
+ "a",
18761
+ "os",
18762
+ "as",
18763
+ "um",
18764
+ "uma",
18765
+ "de",
18766
+ "do",
18767
+ "da",
18768
+ "e",
18769
+ "\xE9",
18770
+ "s\xE3o",
18771
+ "que",
18772
+ "com",
18773
+ "para",
18774
+ "n\xE3o",
18775
+ "sim",
18776
+ "fazer",
18777
+ "criar",
18778
+ "mostrar",
18779
+ "ol\xE1",
18780
+ "obrigado",
18781
+ "bom"
18782
+ ],
18783
+ nl: [
18784
+ "de",
18785
+ "het",
18786
+ "een",
18787
+ "van",
18788
+ "en",
18789
+ "is",
18790
+ "zijn",
18791
+ "niet",
18792
+ "met",
18793
+ "voor",
18794
+ "ja",
18795
+ "nee",
18796
+ "doen",
18797
+ "maken",
18798
+ "tonen",
18799
+ "hallo",
18800
+ "dank",
18801
+ "goed"
18802
+ ]
18803
+ };
18804
+ LANGUAGE_POLICY_MODULE_TYPE = "language-policy";
18805
+ }
18806
+ });
18807
+
18404
18808
  // packages/core/dist/skills/index.js
18405
18809
  var skills_exports = {};
18406
18810
  __export(skills_exports, {
@@ -18408,14 +18812,19 @@ __export(skills_exports, {
18408
18812
  ALL_TOOLS: () => ALL_TOOLS,
18409
18813
  CODING_CATEGORY: () => CODING_CATEGORY,
18410
18814
  CODING_SKILL_CATALOG: () => CODING_SKILL_CATALOG,
18815
+ LANGUAGE_POLICY_MODULE_TYPE: () => LANGUAGE_POLICY_MODULE_TYPE,
18411
18816
  SINGLE_AGENT_IDENTITY_MODULE: () => SINGLE_AGENT_IDENTITY_MODULE,
18412
18817
  SKILL_CATALOG: () => SKILL_CATALOG,
18413
18818
  TOOL_DEFINITIONS: () => TOOL_DEFINITIONS,
18414
18819
  VAULT_TOOL_DEFINITIONS: () => VAULT_TOOL_DEFINITIONS,
18415
18820
  buildCustomParameters: () => buildCustomParameters,
18821
+ buildLanguageDirective: () => buildLanguageDirective,
18822
+ buildLanguagePolicyModule: () => buildLanguagePolicyModule,
18823
+ buildLanguagePolicyModuleFor: () => buildLanguagePolicyModuleFor,
18416
18824
  buildSystemPrompt: () => buildSystemPrompt,
18417
18825
  clearCustomTools: () => clearCustomTools,
18418
18826
  cliToolToEnhanced: () => cliToolToEnhanced,
18827
+ detectResponseLanguage: () => detectResponseLanguage,
18419
18828
  executeTool: () => executeTool,
18420
18829
  findCodingSkillsByCategory: () => findCodingSkillsByCategory,
18421
18830
  findCodingSkillsByTag: () => findCodingSkillsByTag,
@@ -18436,6 +18845,7 @@ __export(skills_exports, {
18436
18845
  registerCustomTool: () => registerCustomTool,
18437
18846
  registerSkill: () => registerSkill,
18438
18847
  resolveAgentSkills: () => resolveAgentSkills,
18848
+ resolveResponseLanguage: () => resolveResponseLanguage,
18439
18849
  resolveSkillDependencies: () => resolveSkillDependencies,
18440
18850
  setWorkspaceStubs: () => setWorkspaceStubs,
18441
18851
  unregisterCustomTool: () => unregisterCustomTool,
@@ -18453,6 +18863,7 @@ var init_skills2 = __esm({
18453
18863
  init_harnessToolBridge();
18454
18864
  init_promptModules();
18455
18865
  init_systemPromptBuilder();
18866
+ init_languagePolicy();
18456
18867
  }
18457
18868
  });
18458
18869
 
@@ -23438,6 +23849,17 @@ async function* runCouncilPure(userMessage, config2, callbacks = {}) {
23438
23849
  const sessionId = config2.sessionId ?? crypto.randomUUID();
23439
23850
  const runMode = config2.runMode ?? "implementation";
23440
23851
  const isDesignPhase = runMode === "design-phase";
23852
+ let councilLanguageModule;
23853
+ try {
23854
+ councilLanguageModule = buildLanguagePolicyModuleFor(userMessage);
23855
+ } catch {
23856
+ councilLanguageModule = {
23857
+ type: "language-policy",
23858
+ title: "Response Language",
23859
+ priority: 5,
23860
+ content: "# Response Language\nReply in the user's language when possible, otherwise Italian."
23861
+ };
23862
+ }
23441
23863
  yield createBrainEvent("council_mode", sessionId, {
23442
23864
  tier: councilTierFromSize(config2.councilSize),
23443
23865
  councilSize: config2.councilSize,
@@ -23561,7 +23983,8 @@ async function* runCouncilPure(userMessage, config2, callbacks = {}) {
23561
23983
  effectiveModel,
23562
23984
  onToolCall: () => {
23563
23985
  toolCalls += 1;
23564
- }
23986
+ },
23987
+ languageModule: councilLanguageModule
23565
23988
  });
23566
23989
  } else if (isDesignPhase) {
23567
23990
  enforceDesignPhaseToolEmissions(agent.id, emittedToolNames);
@@ -23699,7 +24122,8 @@ async function* runCouncilPure(userMessage, config2, callbacks = {}) {
23699
24122
  effectiveModel,
23700
24123
  onToolCall: () => {
23701
24124
  toolCalls += 1;
23702
- }
24125
+ },
24126
+ languageModule: councilLanguageModule
23703
24127
  });
23704
24128
  } else if (isDesignPhase) {
23705
24129
  enforceDesignPhaseToolEmissions(oracle.id, emittedToolNames);
@@ -23867,7 +24291,8 @@ async function* runCouncilPure(userMessage, config2, callbacks = {}) {
23867
24291
  effectiveModel,
23868
24292
  onToolCall: () => {
23869
24293
  toolCalls += 1;
23870
- }
24294
+ },
24295
+ languageModule: councilLanguageModule
23871
24296
  });
23872
24297
  } else if (isDesignPhase) {
23873
24298
  enforceDesignPhaseToolEmissions(chairman.id, emittedToolNames);
@@ -23927,7 +24352,8 @@ async function* runCouncilPure(userMessage, config2, callbacks = {}) {
23927
24352
  onSuccessfulWrite: () => {
23928
24353
  successfulWriteCount += 1;
23929
24354
  },
23930
- onCouncilStatus: callbacks.onCouncilStatus
24355
+ onCouncilStatus: callbacks.onCouncilStatus,
24356
+ languageModule: councilLanguageModule
23931
24357
  });
23932
24358
  }
23933
24359
  }
@@ -23946,7 +24372,8 @@ async function* runCouncilPure(userMessage, config2, callbacks = {}) {
23946
24372
  effectiveModel,
23947
24373
  onToolCall: () => {
23948
24374
  toolCalls += 1;
23949
- }
24375
+ },
24376
+ languageModule: councilLanguageModule
23950
24377
  });
23951
24378
  }
23952
24379
  if (chairmanProjectRoot) {
@@ -23964,7 +24391,8 @@ async function* runCouncilPure(userMessage, config2, callbacks = {}) {
23964
24391
  onToolCall: () => {
23965
24392
  toolCalls += 1;
23966
24393
  },
23967
- onCouncilStatus: callbacks.onCouncilStatus
24394
+ onCouncilStatus: callbacks.onCouncilStatus,
24395
+ languageModule: councilLanguageModule
23968
24396
  });
23969
24397
  }
23970
24398
  }
@@ -24030,7 +24458,8 @@ async function* applyCompletionRetry(args) {
24030
24458
  toolRegistry: args.config.tools,
24031
24459
  providerStream: args.config.providerStream,
24032
24460
  runMode: args.config.runMode,
24033
- retryPrompt: buildImplementationVerifyRetryPrompt(retryTool)
24461
+ retryPrompt: buildImplementationVerifyRetryPrompt(retryTool),
24462
+ languageModule: args.languageModule
24034
24463
  });
24035
24464
  for await (const event of retryGenerator) {
24036
24465
  if (event.type === "tool_execution_start") {
@@ -24088,7 +24517,8 @@ async function* applyImplementationWriteRetry(args) {
24088
24517
  toolRegistry: args.config.tools,
24089
24518
  providerStream: args.config.providerStream,
24090
24519
  runMode: "implementation",
24091
- retryPrompt: buildImplementationWriteRetryPrompt(args.userMessage)
24520
+ retryPrompt: buildImplementationWriteRetryPrompt(args.userMessage),
24521
+ languageModule: args.languageModule
24092
24522
  });
24093
24523
  for await (const event of retryGenerator) {
24094
24524
  if (event.type === "tool_execution_start")
@@ -24157,7 +24587,8 @@ async function* runChairmanDeliveryLoop(args) {
24157
24587
  toolRegistry: args.config.tools,
24158
24588
  providerStream: args.config.providerStream,
24159
24589
  runMode: "implementation",
24160
- retryPrompt: buildDeliveryFixPrompt(blocking, args.userMessage)
24590
+ retryPrompt: buildDeliveryFixPrompt(blocking, args.userMessage),
24591
+ languageModule: args.languageModule
24161
24592
  });
24162
24593
  for await (const event of fixGenerator) {
24163
24594
  if (event.type === "tool_execution_start")
@@ -24255,7 +24686,8 @@ async function* runChairmanFixLoop(args) {
24255
24686
  toolRegistry: args.config.tools,
24256
24687
  providerStream: args.config.providerStream,
24257
24688
  runMode: "implementation",
24258
- retryPrompt: buildMotionFixPrompt(current)
24689
+ retryPrompt: buildMotionFixPrompt(current),
24690
+ languageModule: args.languageModule
24259
24691
  });
24260
24692
  for await (const event of fixGenerator) {
24261
24693
  if (event.type === "tool_execution_start")
@@ -24400,7 +24832,8 @@ async function* applyRetryIfMissing(args) {
24400
24832
  eventBus: args.config.eventBus,
24401
24833
  toolRegistry: args.config.tools,
24402
24834
  providerStream: args.config.providerStream,
24403
- runMode: args.config.runMode
24835
+ runMode: args.config.runMode,
24836
+ languageModule: args.languageModule
24404
24837
  });
24405
24838
  for await (const event of retryGenerator) {
24406
24839
  if (event.type === "tool_execution_start") {
@@ -24422,6 +24855,7 @@ var init_councilApi = __esm({
24422
24855
  init_toolSchemas();
24423
24856
  init_systemPromptBuilder();
24424
24857
  init_tools();
24858
+ init_languagePolicy();
24425
24859
  init_events();
24426
24860
  init_AgentHarness();
24427
24861
  init_modeBanners();
@@ -25820,7 +26254,7 @@ import {
25820
26254
  readdirSync as readdirSync2,
25821
26255
  renameSync as renameSync2
25822
26256
  } from "node:fs";
25823
- import { dirname, join as join13 } from "node:path";
26257
+ import { dirname as dirname2, join as join13 } from "node:path";
25824
26258
  function parseFrontmatter(md) {
25825
26259
  const m = FRONTMATTER_RE.exec(md);
25826
26260
  if (!m) return { meta: {}, body: md };
@@ -26091,7 +26525,7 @@ var init_storage = __esm({
26091
26525
  * The meta object is serialized as YAML frontmatter; body as Markdown.
26092
26526
  */
26093
26527
  write(path34, meta3, body) {
26094
- mkdirSync7(dirname(path34), { recursive: true });
26528
+ mkdirSync7(dirname2(path34), { recursive: true });
26095
26529
  const tmp = path34 + ".tmp-" + process.pid;
26096
26530
  const md = serializeFrontmatter(meta3, body);
26097
26531
  writeFileSync11(tmp, md, "utf8");
@@ -26146,7 +26580,7 @@ import {
26146
26580
  mkdirSync as mkdirSync8,
26147
26581
  renameSync as renameSync3
26148
26582
  } from "node:fs";
26149
- import { join as join14, basename as basename2, dirname as dirname2, relative as relative2 } from "node:path";
26583
+ import { join as join14, basename as basename2, dirname as dirname3, relative as relative2 } from "node:path";
26150
26584
  function createWorkspaceContext(projectRoot = process.cwd()) {
26151
26585
  const rootDir = resolveWorkspaceRoot(projectRoot);
26152
26586
  return {
@@ -26185,7 +26619,7 @@ function readPlan(ctx) {
26185
26619
  }
26186
26620
  function writePlan(ctx, summary) {
26187
26621
  const jsonPath = planJsonPath(ctx);
26188
- mkdirSync8(dirname2(jsonPath), { recursive: true });
26622
+ mkdirSync8(dirname3(jsonPath), { recursive: true });
26189
26623
  const tmp = jsonPath + ".tmp-" + process.pid;
26190
26624
  writeFileSync12(tmp, JSON.stringify(summary, null, 2), "utf8");
26191
26625
  renameSync3(tmp, jsonPath);
@@ -28764,29 +29198,47 @@ __export(prereqChecks_exports, {
28764
29198
  checkAgentGit: () => checkAgentGit,
28765
29199
  checkAgentNode: () => checkAgentNode,
28766
29200
  checkMainNode: () => checkMainNode,
29201
+ isWslBashPath: () => isWslBashPath2,
28767
29202
  runPrereqChecks: () => runPrereqChecks
28768
29203
  });
28769
29204
  import { execSync, spawnSync as spawnSync2 } from "node:child_process";
28770
29205
  import { existsSync as existsSync25 } from "node:fs";
29206
+ import { dirname as dirname4 } from "node:path";
29207
+ function isWslBashPath2(p3) {
29208
+ if (!p3 || typeof p3 !== "string") return false;
29209
+ const n = p3.replace(/\//g, "\\").toLowerCase();
29210
+ if (n.includes("\\windows\\system32\\bash.exe")) return true;
29211
+ if (n.includes("\\windows\\syswow64\\bash.exe")) return true;
29212
+ if (n.includes("\\windowsapps\\bash.exe")) return true;
29213
+ return false;
29214
+ }
29215
+ function acceptBashPath2(p3) {
29216
+ if (!p3 || p3.trim().length === 0) return null;
29217
+ const trimmed = p3.trim();
29218
+ if (isWslBashPath2(trimmed)) return null;
29219
+ if (!existsSyncSafe2(trimmed)) return null;
29220
+ return trimmed;
29221
+ }
28771
29222
  function resolveAgentShellSync() {
28772
29223
  if (process.platform !== "win32") {
28773
29224
  return { bashPath: null, isBash: true, via: "/bin/sh" };
28774
29225
  }
28775
- const envShell = process.env.ZELARI_SHELL;
28776
- if (envShell && envShell.trim().length > 0 && existsSyncSafe2(envShell)) {
28777
- return { bashPath: envShell, isBash: true, via: `bash (${envShell})` };
29226
+ const fromEnv = acceptBashPath2(process.env.ZELARI_SHELL);
29227
+ if (fromEnv) {
29228
+ return { bashPath: fromEnv, isBash: true, via: `bash (${fromEnv})` };
28778
29229
  }
28779
- const sessionShell = process.env.SHELL;
28780
- if (sessionShell && sessionShell.trim().length > 0 && existsSyncSafe2(sessionShell)) {
29230
+ const fromSession = acceptBashPath2(process.env.SHELL);
29231
+ if (fromSession) {
28781
29232
  return {
28782
- bashPath: sessionShell,
29233
+ bashPath: fromSession,
28783
29234
  isBash: true,
28784
- via: `bash (${sessionShell})`
29235
+ via: `bash (${fromSession})`
28785
29236
  };
28786
29237
  }
28787
29238
  for (const p3 of STANDARD_BASH_PATHS2) {
28788
- if (existsSyncSafe2(p3)) {
28789
- return { bashPath: p3, isBash: true, via: `bash (${p3})` };
29239
+ const accepted = acceptBashPath2(p3);
29240
+ if (accepted) {
29241
+ return { bashPath: accepted, isBash: true, via: `bash (${accepted})` };
28790
29242
  }
28791
29243
  }
28792
29244
  try {
@@ -28795,16 +29247,39 @@ function resolveAgentShellSync() {
28795
29247
  windowsHide: true
28796
29248
  });
28797
29249
  if (result.status === 0 && result.stdout) {
28798
- const first = result.stdout.split(/\r?\n/).find((l) => l.trim().length > 0);
28799
- if (first && existsSyncSafe2(first)) {
28800
- const trimmed = first.trim();
28801
- return { bashPath: trimmed, isBash: true, via: `bash (${trimmed})` };
29250
+ for (const line of result.stdout.split(/\r?\n/)) {
29251
+ const accepted = acceptBashPath2(line);
29252
+ if (accepted) {
29253
+ return {
29254
+ bashPath: accepted,
29255
+ isBash: true,
29256
+ via: `bash (${accepted})`
29257
+ };
29258
+ }
28802
29259
  }
28803
29260
  }
28804
29261
  } catch {
28805
29262
  }
28806
29263
  return { bashPath: null, isBash: false, via: "cmd.exe" };
28807
29264
  }
29265
+ function agentProbeEnv() {
29266
+ const env = { ...process.env };
29267
+ try {
29268
+ const nodeDir = dirname4(process.execPath);
29269
+ if (!nodeDir) return env;
29270
+ const sep = process.platform === "win32" ? ";" : ":";
29271
+ const current = env.PATH ?? env.Path ?? "";
29272
+ const parts = current.split(sep).filter((p3) => p3.length > 0);
29273
+ const has = parts.some(
29274
+ (p3) => p3.toLowerCase() === nodeDir.toLowerCase()
29275
+ );
29276
+ if (!has) {
29277
+ env.PATH = `${nodeDir}${sep}${current}`;
29278
+ }
29279
+ } catch {
29280
+ }
29281
+ return env;
29282
+ }
28808
29283
  function existsSyncSafe2(p3) {
28809
29284
  try {
28810
29285
  return existsSync25(p3);
@@ -28815,12 +29290,14 @@ function existsSyncSafe2(p3) {
28815
29290
  function probeTool(tool) {
28816
29291
  const shell = resolveAgentShellSync();
28817
29292
  let stdout = "";
29293
+ const env = agentProbeEnv();
28818
29294
  if (shell.bashPath) {
28819
29295
  try {
28820
29296
  const r = spawnSync2(shell.bashPath, ["-c", `${tool} --version`], {
28821
29297
  encoding: "utf8",
28822
29298
  stdio: ["ignore", "pipe", "ignore"],
28823
- windowsHide: true
29299
+ windowsHide: true,
29300
+ env
28824
29301
  });
28825
29302
  if (r.status === 0) stdout = (r.stdout || "").trim();
28826
29303
  } catch {
@@ -28829,7 +29306,8 @@ function probeTool(tool) {
28829
29306
  try {
28830
29307
  stdout = execSync(`${tool} --version`, {
28831
29308
  encoding: "utf8",
28832
- stdio: ["ignore", "pipe", "ignore"]
29309
+ stdio: ["ignore", "pipe", "ignore"],
29310
+ env
28833
29311
  }).trim();
28834
29312
  } catch {
28835
29313
  }
@@ -28837,7 +29315,8 @@ function probeTool(tool) {
28837
29315
  try {
28838
29316
  stdout = execSync(`${tool} --version`, {
28839
29317
  encoding: "utf8",
28840
- stdio: ["ignore", "pipe", "ignore"]
29318
+ stdio: ["ignore", "pipe", "ignore"],
29319
+ env
28841
29320
  }).trim();
28842
29321
  } catch {
28843
29322
  }
@@ -28852,14 +29331,21 @@ function probeTool(tool) {
28852
29331
  function nodeMissingHint() {
28853
29332
  const shell = resolveAgentShellSync();
28854
29333
  if (process.platform === "win32") {
29334
+ if (!shell.isBash) {
29335
+ return `node is not reachable from the agent's shell (${shell.via}).
29336
+ Install Node >= ${MIN_NODE_MAJOR} (https://nodejs.org) and ensure it is
29337
+ on PATH, then open a NEW terminal. Optional: install Git for
29338
+ Windows so the agent can use real bash (https://git-scm.com/download/win).`;
29339
+ }
28855
29340
  return `node is not reachable from the agent's shell (${shell.via}).
28856
29341
  This usually means Node was installed for "current user" only,
28857
- while Git Bash inherits the SYSTEM Path. Fix (pick one):
29342
+ while Git Bash sees a different Path. Fix (pick one):
28858
29343
  - Reinstall Node (https://nodejs.org) and choose
28859
29344
  "Add to PATH for all users", OR
28860
- - Add C:\\Program Files\\nodejs\\ to the SYSTEM Path
28861
- (System Properties \u2192 Environment Variables \u2192 Path), OR
28862
- - Set ZELARI_SHELL to a bash that already sees node.`;
29345
+ - Add your nodejs folder to the User or System Path, OR
29346
+ - Set ZELARI_SHELL to a bash that already sees node.
29347
+ Note: WSL's C:\\Windows\\System32\\bash.exe is NOT a valid agent
29348
+ shell \u2014 install Git for Windows instead.`;
28863
29349
  }
28864
29350
  return `node is not on the agent's shell PATH.
28865
29351
  Install Node >= ${MIN_NODE_MAJOR} (https://nodejs.org) or, if you use
@@ -33163,12 +33649,32 @@ function finalizeStreamingAssistant(setMessages) {
33163
33649
  });
33164
33650
  }
33165
33651
 
33652
+ // src/cli/utils/envNumber.ts
33653
+ function envNumber(raw, opts) {
33654
+ const { default: def, min, max } = opts;
33655
+ if (raw === void 0 || raw === null) return def;
33656
+ const trimmed = raw.trim();
33657
+ if (trimmed.length === 0) return def;
33658
+ if (trimmed.toLowerCase() === "undefined" || trimmed.toLowerCase() === "null") return def;
33659
+ const parsed = Number.parseInt(trimmed, 10);
33660
+ if (!Number.isFinite(parsed)) return def;
33661
+ const absStr = `${Math.abs(parsed)}`;
33662
+ const body = trimmed.replace(/^[-+]/, "").replace(/^0+(?=\d)/, "");
33663
+ if (body !== absStr) return def;
33664
+ const firstChar = trimmed[0];
33665
+ const expectedSign = parsed < 0 ? "-" : /[0-9+]/.test(firstChar ?? "");
33666
+ const actualSign = parsed < 0 ? firstChar === "-" : firstChar !== "-";
33667
+ if (!expectedSign || !actualSign) return def;
33668
+ let clamped = parsed;
33669
+ if (min !== void 0 && clamped < min) clamped = min;
33670
+ if (max !== void 0 && clamped > max) clamped = max;
33671
+ return clamped;
33672
+ }
33673
+
33166
33674
  // src/cli/hooks/historyCompaction.ts
33167
33675
  var COMPACT_MARKER = "[history] Earlier turns were compacted to stay within the context budget.";
33168
33676
  function resolveMaxMessages(opts) {
33169
- const raw = process.env.ZELARI_HISTORY_TURNS;
33170
- const parsed = raw ? Number.parseInt(raw, 10) : 6;
33171
- const envTurns = Number.isFinite(parsed) && parsed >= 0 ? parsed : 6;
33677
+ const envTurns = envNumber(process.env.ZELARI_HISTORY_TURNS, { default: 6, min: 0 });
33172
33678
  const turns = opts?.maxMessages ? Math.ceil(opts.maxMessages / 4) : envTurns;
33173
33679
  if (turns <= 0) return 0;
33174
33680
  return turns * 4;
@@ -33379,21 +33885,24 @@ function useChatTurn(params) {
33379
33885
  const workspaceContext = [workspaceSummary, zelariReadHint].filter(Boolean).join("\n\n");
33380
33886
  let systemPrompt;
33381
33887
  try {
33888
+ const languageModule = buildLanguagePolicyModuleFor(userText);
33382
33889
  systemPrompt = buildSystemPrompt(singleAgentRole, {
33383
33890
  tools: getAllTools(),
33384
33891
  toolNames: toolListNames,
33385
33892
  aiConfig: {
33386
33893
  enabledSkills: [],
33387
33894
  enabledTools: toolListNames,
33388
- customPromptModules: [SINGLE_AGENT_IDENTITY_MODULE],
33895
+ customPromptModules: [SINGLE_AGENT_IDENTITY_MODULE, languageModule],
33389
33896
  agentSkillConfigs: []
33390
33897
  },
33391
33898
  workspaceContext: workspaceContext || void 0,
33392
33899
  ragContext: planSummary || void 0
33393
33900
  });
33394
33901
  } catch {
33902
+ const languageModule = buildLanguagePolicyModuleFor(userText);
33395
33903
  systemPrompt = [
33396
33904
  SINGLE_AGENT_IDENTITY_MODULE.content,
33905
+ languageModule.content,
33397
33906
  shellContextBlock,
33398
33907
  "# Available Tools",
33399
33908
  "You can call these tools. Use them to take action and gather information autonomously:",
@@ -33402,16 +33911,14 @@ function useChatTurn(params) {
33402
33911
  ...planSummary ? ["", planSummary] : []
33403
33912
  ].join("\n");
33404
33913
  }
33405
- const maxToolCallsPerTurn = (() => {
33406
- const raw = process.env.ZELARI_MAX_TOOL_CALLS;
33407
- const n = raw ? Number.parseInt(raw, 10) : 25;
33408
- return Number.isFinite(n) && n > 0 ? n : 25;
33409
- })();
33410
- const maxToolLoopIterations = (() => {
33411
- const raw = process.env.ZELARI_MAX_TOOL_LOOP_ITERATIONS;
33412
- const n = raw ? Number.parseInt(raw, 10) : 90;
33413
- return Number.isFinite(n) && n > 0 ? n : 90;
33414
- })();
33914
+ const maxToolCallsPerTurn = envNumber(process.env.ZELARI_MAX_TOOL_CALLS, {
33915
+ default: 25,
33916
+ min: 1
33917
+ });
33918
+ const maxToolLoopIterations = envNumber(process.env.ZELARI_MAX_TOOL_LOOP_ITERATIONS, {
33919
+ default: 90,
33920
+ min: 1
33921
+ });
33415
33922
  const harness2 = new AgentHarness({
33416
33923
  model: envConfig.model,
33417
33924
  provider: "openai-compatible",
@@ -33748,11 +34255,10 @@ async function dispatchCouncilPromptImpl(text, deps, overrides = {}) {
33748
34255
  const councilFeedbackStore = new FeedbackStore2();
33749
34256
  let streamContent = "";
33750
34257
  let streamMemberId = null;
33751
- const councilMaxToolCalls = (() => {
33752
- const raw = process.env.ZELARI_MAX_TOOL_CALLS;
33753
- const n = raw ? Number.parseInt(raw, 10) : 15;
33754
- return Number.isFinite(n) && n > 0 ? n : 15;
33755
- })();
34258
+ const councilMaxToolCalls = envNumber(process.env.ZELARI_MAX_TOOL_CALLS, {
34259
+ default: 15,
34260
+ min: 1
34261
+ });
33756
34262
  let membersCompleted = 0;
33757
34263
  let chairmanProducedOutput = false;
33758
34264
  let chairmanSynthesisText = "";
@@ -34129,11 +34635,10 @@ async function runZelariMissionInTui(userMessage, deps, emit) {
34129
34635
  hasPlan: hasWorkspacePlan2(projectRoot)
34130
34636
  });
34131
34637
  const memory = await getMemoryBackend2(projectRoot);
34132
- const chairmanBudget = (() => {
34133
- const raw = process.env.ZELARI_MODE_MAX_TOOLS_LUCIFER;
34134
- const n = raw ? Number.parseInt(raw, 10) : 30;
34135
- return Number.isFinite(n) && n > 0 ? n : 30;
34136
- })();
34638
+ const chairmanBudget = envNumber(process.env.ZELARI_MODE_MAX_TOOLS_LUCIFER, {
34639
+ default: 30,
34640
+ min: 1
34641
+ });
34137
34642
  try {
34138
34643
  await runZelariMission2(userMessage, brief, {
34139
34644
  projectRoot,
@@ -37295,6 +37800,7 @@ function emitEvent(event) {
37295
37800
  // src/cli/runHeadless.ts
37296
37801
  init_harness();
37297
37802
  init_toolRegistry();
37803
+ init_skills2();
37298
37804
  async function runHeadless(opts) {
37299
37805
  const { provider, model } = resolveHeadlessProvider(opts);
37300
37806
  const key = await resolveHeadlessKey(provider);
@@ -37322,11 +37828,61 @@ async function runHeadlessSingle(opts, provider, model, providerStream) {
37322
37828
  description: t.function.description,
37323
37829
  parameters: t.function.parameters
37324
37830
  }));
37325
- const systemPrompt = [
37326
- "You are zelari-code, a CLI coding agent. Be concise and direct.",
37327
- "When the user asks you to write code, debug, or explore, be proactive: list files and read key files to understand the project.",
37328
- "When you finish a task, briefly summarize what you did."
37329
- ].join("\n");
37831
+ const toolNames = tools.map((t) => t.name);
37832
+ let systemPrompt;
37833
+ let languageDirectiveContent;
37834
+ try {
37835
+ languageDirectiveContent = buildLanguagePolicyModuleFor(opts.task).content;
37836
+ } catch {
37837
+ languageDirectiveContent = "# Response Language\nReply in the user's language when possible, otherwise Italian.";
37838
+ }
37839
+ try {
37840
+ const headlessRole = {
37841
+ id: "single",
37842
+ name: "Zelari Code",
37843
+ codename: "zelari",
37844
+ role: "headless coding agent",
37845
+ color: "#00d9a3",
37846
+ avatar: "\u25C6",
37847
+ tools: toolNames,
37848
+ // v1.7.0 fix (agy audit): include platform/shell context in the role
37849
+ // prompt so the agent knows its cwd and shell — previously this was
37850
+ // empty (systemPrompt: '') and the agent lacked the platform block
37851
+ // the TUI single-agent path gets.
37852
+ systemPrompt: [
37853
+ "# Platform",
37854
+ `platform: ${process.platform}`,
37855
+ `shell: ${process.platform === "win32" ? "cmd.exe / Git Bash (auto-detected)" : "/bin/sh"}`,
37856
+ "",
37857
+ "# Working Directory",
37858
+ `You are running in: ${process.cwd()}`,
37859
+ "All relative file paths are resolved against this directory.",
37860
+ "The shell is NON-INTERACTIVE (stdin closed): pass non-interactive flags (--yes, --force, --template)."
37861
+ ].join("\n")
37862
+ };
37863
+ systemPrompt = buildSystemPrompt(headlessRole, {
37864
+ tools: getAllTools(),
37865
+ toolNames,
37866
+ aiConfig: {
37867
+ enabledSkills: [],
37868
+ enabledTools: toolNames,
37869
+ customPromptModules: [SINGLE_AGENT_IDENTITY_MODULE, {
37870
+ type: "language-policy",
37871
+ title: "Response Language",
37872
+ priority: 5,
37873
+ content: languageDirectiveContent
37874
+ }],
37875
+ agentSkillConfigs: []
37876
+ }
37877
+ });
37878
+ } catch {
37879
+ systemPrompt = [
37880
+ "You are zelari-code, a CLI coding agent. Be concise and direct.",
37881
+ "When the user asks you to write code, debug, or explore, be proactive: list files and read key files to understand the project.",
37882
+ "When you finish a task, briefly summarize what you did.",
37883
+ languageDirectiveContent
37884
+ ].join("\n");
37885
+ }
37330
37886
  const harness = new AgentHarness({
37331
37887
  model,
37332
37888
  provider,
@@ -37339,9 +37895,8 @@ async function runHeadlessSingle(opts, provider, model, providerStream) {
37339
37895
  toolRegistry,
37340
37896
  providerStream,
37341
37897
  maxToolLoopIterations: (() => {
37342
- const raw = process.env.ZELARI_MAX_TOOL_LOOP_ITERATIONS;
37343
- const n = raw ? Number.parseInt(raw, 10) : 30;
37344
- return Number.isFinite(n) && n > 0 ? n : 30;
37898
+ const n = envNumber(process.env.ZELARI_MAX_TOOL_LOOP_ITERATIONS, { default: 30, min: 1 });
37899
+ return n;
37345
37900
  })()
37346
37901
  });
37347
37902
  let finalReason = "completed";