zelari-code 1.6.0 → 1.7.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.
@@ -18401,6 +18401,366 @@ var init_systemPromptBuilder = __esm({
18401
18401
  }
18402
18402
  });
18403
18403
 
18404
+ // packages/core/dist/agents/languagePolicy.js
18405
+ function isLetter(cp) {
18406
+ return cp >= 65 && cp <= 90 || // A-Z
18407
+ cp >= 97 && cp <= 122 || // a-z
18408
+ cp >= 192 && cp <= 591 || // Latin-1 supplement + Extended A/B
18409
+ cp >= 7680 && cp <= 7935 || // Latin Extended Additional
18410
+ cp >= 880 && cp <= 1023 || // Greek
18411
+ cp >= 1024 && cp <= 1279 || // Cyrillic
18412
+ cp >= 1424 && cp <= 1535 || // Hebrew
18413
+ cp >= 1536 && cp <= 1791 || // Arabic
18414
+ cp >= 2304 && cp <= 2431 || // Devanagari
18415
+ cp >= 12352 && cp <= 12543 || // Hiragana + Katakana
18416
+ cp >= 19968 && cp <= 40959 || // CJK Unified
18417
+ cp >= 44032 && cp <= 55215;
18418
+ }
18419
+ function isLatinChar(cp) {
18420
+ return cp >= 65 && cp <= 90 || // A-Z
18421
+ cp >= 97 && cp <= 122 || // a-z
18422
+ cp >= 192 && cp <= 591 || // Latin-1 supplement + Extended A/B
18423
+ cp >= 7680 && cp <= 7935;
18424
+ }
18425
+ function detectResponseLanguage(text) {
18426
+ if (!text || text.trim().length === 0)
18427
+ return "it";
18428
+ const stripped = text.replace(/```[\s\S]*?```/g, " ").replace(/`[^`]*`/g, " ").toLowerCase();
18429
+ if (stripped.trim().length === 0)
18430
+ return "it";
18431
+ let totalLetters = 0;
18432
+ let nonLatinLetters = 0;
18433
+ for (let i = 0; i < stripped.length; i++) {
18434
+ const cp = stripped.codePointAt(i);
18435
+ if (cp === void 0)
18436
+ continue;
18437
+ if (!isLetter(cp))
18438
+ continue;
18439
+ totalLetters += 1;
18440
+ if (!isLatinChar(cp))
18441
+ nonLatinLetters += 1;
18442
+ }
18443
+ const nonLatinRatio = totalLetters > 0 ? nonLatinLetters / totalLetters : 0;
18444
+ const SCRIPT_DOMINANCE_THRESHOLD = 0.3;
18445
+ if (nonLatinRatio >= SCRIPT_DOMINANCE_THRESHOLD) {
18446
+ for (const { lang, ranges } of SCRIPT_RANGES) {
18447
+ for (const [lo, hi] of ranges) {
18448
+ for (let i = 0; i < stripped.length; i++) {
18449
+ const cp = stripped.codePointAt(i);
18450
+ if (cp !== void 0 && cp >= lo && cp <= hi)
18451
+ return lang;
18452
+ }
18453
+ }
18454
+ }
18455
+ }
18456
+ const ACCENT_VOTES = [
18457
+ // Italian-specific
18458
+ { lang: "it", weight: 2, chars: "\xE0\xE8\xE9\xEC\xF2\xF3\xF9" },
18459
+ // French-only accents
18460
+ { lang: "fr", weight: 2, chars: "\xE2\xE7\xEA\xEB\xEE\xEF\xF4\xFB\u0153\xE6" },
18461
+ // German-only
18462
+ { lang: "de", weight: 3, chars: "\xE4\xF6\xFC\xDF" },
18463
+ // Spanish-only (ñ is the discriminator)
18464
+ { lang: "es", weight: 3, chars: "\xF1\xE1\xED\xF3\xFA\xBF\xA1" },
18465
+ // Portuguese-only (ã/õ are the discriminator)
18466
+ { lang: "pt", weight: 3, chars: "\xE3\xF5\xE1\xE9\xED\xF3\xFA\xE2\xEA\xF4" },
18467
+ // Dutch-only
18468
+ { lang: "nl", weight: 2, chars: "\xE1\xE8\xE9\xEB\xED\xF3\xF6\xFA\xFC" }
18469
+ ];
18470
+ const accentScores = {};
18471
+ for (const { lang, weight, chars } of ACCENT_VOTES) {
18472
+ let hits = 0;
18473
+ for (const c of chars)
18474
+ if (stripped.includes(c))
18475
+ hits += 1;
18476
+ if (hits > 0)
18477
+ accentScores[lang] = (accentScores[lang] ?? 0) + weight * hits;
18478
+ }
18479
+ const accentOrdered = Object.entries(accentScores).sort((a, b) => b[1] - a[1]).map(([l]) => l);
18480
+ if (accentOrdered.length > 0 && accentOrdered.length === 1) {
18481
+ return accentOrdered[0];
18482
+ }
18483
+ if (accentOrdered.length > 1) {
18484
+ return accentOrdered[0];
18485
+ }
18486
+ const tokens = stripped.split(/[\s,;.!?()\[\]{}<>:'"\\/]+/).filter((t) => t.length > 1);
18487
+ if (tokens.length === 0)
18488
+ return "it";
18489
+ const scores = {};
18490
+ for (const [lang, words] of Object.entries(FUNCTION_WORDS)) {
18491
+ let hits = 0;
18492
+ const tokenSet = new Set(tokens);
18493
+ for (const w of words) {
18494
+ if (tokenSet.has(w))
18495
+ hits += 1;
18496
+ }
18497
+ if (hits > 0)
18498
+ scores[lang] = hits;
18499
+ }
18500
+ const ordered = Object.entries(scores).sort((a, b) => b[1] - a[1] || (a[0] === "it" ? -1 : b[0] === "it" ? 1 : 0)).map(([l]) => l);
18501
+ if (ordered.length > 0)
18502
+ return ordered[0];
18503
+ return "it";
18504
+ }
18505
+ function resolveResponseLanguage(text, env = process.env) {
18506
+ const raw = env.ZELARI_RESPONSE_LANG?.trim().toLowerCase();
18507
+ if (raw && raw !== "auto") {
18508
+ if (raw in LANGUAGE_LABELS)
18509
+ return raw;
18510
+ const sink = globalThis.__zelariLangWarnSink ?? ((msg) => console.warn(`[zelari-code] ${msg}`));
18511
+ 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.`);
18512
+ }
18513
+ return detectResponseLanguage(text);
18514
+ }
18515
+ function buildLanguageDirective(lang) {
18516
+ const label = LANGUAGE_LABELS[lang];
18517
+ return `# Response Language \u2014 ${label}
18518
+
18519
+ 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):
18520
+
18521
+ - 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}.
18522
+ - 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.
18523
+ - 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.
18524
+ - For clarifying questions (---QUESTION--- blocks), write the \`question\` and \`choices\` fields in ${label} so the picker UI matches the user's language.
18525
+ - 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.`;
18526
+ }
18527
+ function buildLanguagePolicyModule(lang) {
18528
+ return {
18529
+ type: LANGUAGE_POLICY_MODULE_TYPE,
18530
+ title: `Response Language (${LANGUAGE_LABELS[lang]})`,
18531
+ priority: 5,
18532
+ content: buildLanguageDirective(lang)
18533
+ };
18534
+ }
18535
+ function buildLanguagePolicyModuleFor(userText, env = process.env) {
18536
+ return buildLanguagePolicyModule(resolveResponseLanguage(userText, env));
18537
+ }
18538
+ var LANGUAGE_LABELS, SCRIPT_RANGES, FUNCTION_WORDS, LANGUAGE_POLICY_MODULE_TYPE;
18539
+ var init_languagePolicy = __esm({
18540
+ "packages/core/dist/agents/languagePolicy.js"() {
18541
+ "use strict";
18542
+ LANGUAGE_LABELS = {
18543
+ it: "Italian",
18544
+ en: "English",
18545
+ fr: "French",
18546
+ es: "Spanish",
18547
+ de: "German",
18548
+ pt: "Portuguese",
18549
+ nl: "Dutch",
18550
+ zh: "Chinese (Simplified)",
18551
+ ja: "Japanese",
18552
+ ko: "Korean",
18553
+ ru: "Russian",
18554
+ ar: "Arabic"
18555
+ };
18556
+ SCRIPT_RANGES = [
18557
+ // v1.7.0 fix: ja/kr MUST come before zh — kanji (CJK Unified) and hangul
18558
+ // glyphs can fall in either's range when both languages are present, but
18559
+ // hiragana/katakana (ja) and hangul jamo (kr) are unique owners. A mixed
18560
+ // string like "こんにちは、関数の作り方を…" had kanji that triggered the
18561
+ // zh range first, returning 'zh' for a clearly-Japanese message.
18562
+ { lang: "ja", ranges: [[12352, 12447], [12448, 12543]] },
18563
+ { lang: "ko", ranges: [[44032, 55215], [4352, 4607]] },
18564
+ { lang: "zh", ranges: [[19968, 40959]] },
18565
+ { lang: "ar", ranges: [[1536, 1791]] },
18566
+ { lang: "ru", ranges: [[1024, 1279]] }
18567
+ ];
18568
+ FUNCTION_WORDS = {
18569
+ it: [
18570
+ "il",
18571
+ "lo",
18572
+ "la",
18573
+ "gli",
18574
+ "le",
18575
+ "un",
18576
+ "uno",
18577
+ "una",
18578
+ "di",
18579
+ "del",
18580
+ "della",
18581
+ "dei",
18582
+ "e",
18583
+ "che",
18584
+ "chi",
18585
+ "come",
18586
+ "con",
18587
+ "per",
18588
+ "non",
18589
+ "sono",
18590
+ "questo",
18591
+ "questa",
18592
+ "quello",
18593
+ "quella",
18594
+ "fare",
18595
+ "fai",
18596
+ "crea",
18597
+ "creare",
18598
+ "mostra",
18599
+ "ciao",
18600
+ "grazie",
18601
+ "bene",
18602
+ "male",
18603
+ "puoi",
18604
+ "come",
18605
+ "cosa",
18606
+ "questo"
18607
+ ],
18608
+ en: [
18609
+ "the",
18610
+ "a",
18611
+ "an",
18612
+ "of",
18613
+ "and",
18614
+ "is",
18615
+ "are",
18616
+ "was",
18617
+ "were",
18618
+ "to",
18619
+ "for",
18620
+ "with",
18621
+ "that",
18622
+ "this",
18623
+ "these",
18624
+ "those",
18625
+ "do",
18626
+ "does",
18627
+ "make",
18628
+ "create",
18629
+ "show",
18630
+ "hello",
18631
+ "help",
18632
+ "please",
18633
+ "thanks",
18634
+ "how",
18635
+ "what",
18636
+ "where",
18637
+ "why"
18638
+ ],
18639
+ fr: [
18640
+ "le",
18641
+ "la",
18642
+ "les",
18643
+ "un",
18644
+ "une",
18645
+ "des",
18646
+ "de",
18647
+ "du",
18648
+ "et",
18649
+ "est",
18650
+ "sont",
18651
+ "que",
18652
+ "qui",
18653
+ "avec",
18654
+ "pour",
18655
+ "dans",
18656
+ "pas",
18657
+ "oui",
18658
+ "non",
18659
+ "faire",
18660
+ "cr\xE9er",
18661
+ "bonjour",
18662
+ "merci",
18663
+ "bien"
18664
+ ],
18665
+ es: [
18666
+ "el",
18667
+ "la",
18668
+ "los",
18669
+ "las",
18670
+ "un",
18671
+ "una",
18672
+ "de",
18673
+ "del",
18674
+ "y",
18675
+ "es",
18676
+ "son",
18677
+ "que",
18678
+ "con",
18679
+ "por",
18680
+ "para",
18681
+ "no",
18682
+ "s\xED",
18683
+ "hacer",
18684
+ "crear",
18685
+ "mostrar",
18686
+ "hola",
18687
+ "gracias",
18688
+ "bueno"
18689
+ ],
18690
+ de: [
18691
+ "der",
18692
+ "die",
18693
+ "das",
18694
+ "den",
18695
+ "dem",
18696
+ "des",
18697
+ "und",
18698
+ "ist",
18699
+ "sind",
18700
+ "nicht",
18701
+ "mit",
18702
+ "f\xFCr",
18703
+ "auf",
18704
+ "zu",
18705
+ "ja",
18706
+ "nein",
18707
+ "machen",
18708
+ "erstellen",
18709
+ "zeigen",
18710
+ "hallo",
18711
+ "danke",
18712
+ "gut"
18713
+ ],
18714
+ pt: [
18715
+ "o",
18716
+ "a",
18717
+ "os",
18718
+ "as",
18719
+ "um",
18720
+ "uma",
18721
+ "de",
18722
+ "do",
18723
+ "da",
18724
+ "e",
18725
+ "\xE9",
18726
+ "s\xE3o",
18727
+ "que",
18728
+ "com",
18729
+ "para",
18730
+ "n\xE3o",
18731
+ "sim",
18732
+ "fazer",
18733
+ "criar",
18734
+ "mostrar",
18735
+ "ol\xE1",
18736
+ "obrigado",
18737
+ "bom"
18738
+ ],
18739
+ nl: [
18740
+ "de",
18741
+ "het",
18742
+ "een",
18743
+ "van",
18744
+ "en",
18745
+ "is",
18746
+ "zijn",
18747
+ "niet",
18748
+ "met",
18749
+ "voor",
18750
+ "ja",
18751
+ "nee",
18752
+ "doen",
18753
+ "maken",
18754
+ "tonen",
18755
+ "hallo",
18756
+ "dank",
18757
+ "goed"
18758
+ ]
18759
+ };
18760
+ LANGUAGE_POLICY_MODULE_TYPE = "language-policy";
18761
+ }
18762
+ });
18763
+
18404
18764
  // packages/core/dist/skills/index.js
18405
18765
  var skills_exports = {};
18406
18766
  __export(skills_exports, {
@@ -18408,14 +18768,19 @@ __export(skills_exports, {
18408
18768
  ALL_TOOLS: () => ALL_TOOLS,
18409
18769
  CODING_CATEGORY: () => CODING_CATEGORY,
18410
18770
  CODING_SKILL_CATALOG: () => CODING_SKILL_CATALOG,
18771
+ LANGUAGE_POLICY_MODULE_TYPE: () => LANGUAGE_POLICY_MODULE_TYPE,
18411
18772
  SINGLE_AGENT_IDENTITY_MODULE: () => SINGLE_AGENT_IDENTITY_MODULE,
18412
18773
  SKILL_CATALOG: () => SKILL_CATALOG,
18413
18774
  TOOL_DEFINITIONS: () => TOOL_DEFINITIONS,
18414
18775
  VAULT_TOOL_DEFINITIONS: () => VAULT_TOOL_DEFINITIONS,
18415
18776
  buildCustomParameters: () => buildCustomParameters,
18777
+ buildLanguageDirective: () => buildLanguageDirective,
18778
+ buildLanguagePolicyModule: () => buildLanguagePolicyModule,
18779
+ buildLanguagePolicyModuleFor: () => buildLanguagePolicyModuleFor,
18416
18780
  buildSystemPrompt: () => buildSystemPrompt,
18417
18781
  clearCustomTools: () => clearCustomTools,
18418
18782
  cliToolToEnhanced: () => cliToolToEnhanced,
18783
+ detectResponseLanguage: () => detectResponseLanguage,
18419
18784
  executeTool: () => executeTool,
18420
18785
  findCodingSkillsByCategory: () => findCodingSkillsByCategory,
18421
18786
  findCodingSkillsByTag: () => findCodingSkillsByTag,
@@ -18436,6 +18801,7 @@ __export(skills_exports, {
18436
18801
  registerCustomTool: () => registerCustomTool,
18437
18802
  registerSkill: () => registerSkill,
18438
18803
  resolveAgentSkills: () => resolveAgentSkills,
18804
+ resolveResponseLanguage: () => resolveResponseLanguage,
18439
18805
  resolveSkillDependencies: () => resolveSkillDependencies,
18440
18806
  setWorkspaceStubs: () => setWorkspaceStubs,
18441
18807
  unregisterCustomTool: () => unregisterCustomTool,
@@ -18453,6 +18819,7 @@ var init_skills2 = __esm({
18453
18819
  init_harnessToolBridge();
18454
18820
  init_promptModules();
18455
18821
  init_systemPromptBuilder();
18822
+ init_languagePolicy();
18456
18823
  }
18457
18824
  });
18458
18825
 
@@ -23438,6 +23805,17 @@ async function* runCouncilPure(userMessage, config2, callbacks = {}) {
23438
23805
  const sessionId = config2.sessionId ?? crypto.randomUUID();
23439
23806
  const runMode = config2.runMode ?? "implementation";
23440
23807
  const isDesignPhase = runMode === "design-phase";
23808
+ let councilLanguageModule;
23809
+ try {
23810
+ councilLanguageModule = buildLanguagePolicyModuleFor(userMessage);
23811
+ } catch {
23812
+ councilLanguageModule = {
23813
+ type: "language-policy",
23814
+ title: "Response Language",
23815
+ priority: 5,
23816
+ content: "# Response Language\nReply in the user's language when possible, otherwise Italian."
23817
+ };
23818
+ }
23441
23819
  yield createBrainEvent("council_mode", sessionId, {
23442
23820
  tier: councilTierFromSize(config2.councilSize),
23443
23821
  councilSize: config2.councilSize,
@@ -23561,7 +23939,8 @@ async function* runCouncilPure(userMessage, config2, callbacks = {}) {
23561
23939
  effectiveModel,
23562
23940
  onToolCall: () => {
23563
23941
  toolCalls += 1;
23564
- }
23942
+ },
23943
+ languageModule: councilLanguageModule
23565
23944
  });
23566
23945
  } else if (isDesignPhase) {
23567
23946
  enforceDesignPhaseToolEmissions(agent.id, emittedToolNames);
@@ -23699,7 +24078,8 @@ async function* runCouncilPure(userMessage, config2, callbacks = {}) {
23699
24078
  effectiveModel,
23700
24079
  onToolCall: () => {
23701
24080
  toolCalls += 1;
23702
- }
24081
+ },
24082
+ languageModule: councilLanguageModule
23703
24083
  });
23704
24084
  } else if (isDesignPhase) {
23705
24085
  enforceDesignPhaseToolEmissions(oracle.id, emittedToolNames);
@@ -23867,7 +24247,8 @@ async function* runCouncilPure(userMessage, config2, callbacks = {}) {
23867
24247
  effectiveModel,
23868
24248
  onToolCall: () => {
23869
24249
  toolCalls += 1;
23870
- }
24250
+ },
24251
+ languageModule: councilLanguageModule
23871
24252
  });
23872
24253
  } else if (isDesignPhase) {
23873
24254
  enforceDesignPhaseToolEmissions(chairman.id, emittedToolNames);
@@ -23927,7 +24308,8 @@ async function* runCouncilPure(userMessage, config2, callbacks = {}) {
23927
24308
  onSuccessfulWrite: () => {
23928
24309
  successfulWriteCount += 1;
23929
24310
  },
23930
- onCouncilStatus: callbacks.onCouncilStatus
24311
+ onCouncilStatus: callbacks.onCouncilStatus,
24312
+ languageModule: councilLanguageModule
23931
24313
  });
23932
24314
  }
23933
24315
  }
@@ -23946,7 +24328,8 @@ async function* runCouncilPure(userMessage, config2, callbacks = {}) {
23946
24328
  effectiveModel,
23947
24329
  onToolCall: () => {
23948
24330
  toolCalls += 1;
23949
- }
24331
+ },
24332
+ languageModule: councilLanguageModule
23950
24333
  });
23951
24334
  }
23952
24335
  if (chairmanProjectRoot) {
@@ -23964,7 +24347,8 @@ async function* runCouncilPure(userMessage, config2, callbacks = {}) {
23964
24347
  onToolCall: () => {
23965
24348
  toolCalls += 1;
23966
24349
  },
23967
- onCouncilStatus: callbacks.onCouncilStatus
24350
+ onCouncilStatus: callbacks.onCouncilStatus,
24351
+ languageModule: councilLanguageModule
23968
24352
  });
23969
24353
  }
23970
24354
  }
@@ -24030,7 +24414,8 @@ async function* applyCompletionRetry(args) {
24030
24414
  toolRegistry: args.config.tools,
24031
24415
  providerStream: args.config.providerStream,
24032
24416
  runMode: args.config.runMode,
24033
- retryPrompt: buildImplementationVerifyRetryPrompt(retryTool)
24417
+ retryPrompt: buildImplementationVerifyRetryPrompt(retryTool),
24418
+ languageModule: args.languageModule
24034
24419
  });
24035
24420
  for await (const event of retryGenerator) {
24036
24421
  if (event.type === "tool_execution_start") {
@@ -24088,7 +24473,8 @@ async function* applyImplementationWriteRetry(args) {
24088
24473
  toolRegistry: args.config.tools,
24089
24474
  providerStream: args.config.providerStream,
24090
24475
  runMode: "implementation",
24091
- retryPrompt: buildImplementationWriteRetryPrompt(args.userMessage)
24476
+ retryPrompt: buildImplementationWriteRetryPrompt(args.userMessage),
24477
+ languageModule: args.languageModule
24092
24478
  });
24093
24479
  for await (const event of retryGenerator) {
24094
24480
  if (event.type === "tool_execution_start")
@@ -24157,7 +24543,8 @@ async function* runChairmanDeliveryLoop(args) {
24157
24543
  toolRegistry: args.config.tools,
24158
24544
  providerStream: args.config.providerStream,
24159
24545
  runMode: "implementation",
24160
- retryPrompt: buildDeliveryFixPrompt(blocking, args.userMessage)
24546
+ retryPrompt: buildDeliveryFixPrompt(blocking, args.userMessage),
24547
+ languageModule: args.languageModule
24161
24548
  });
24162
24549
  for await (const event of fixGenerator) {
24163
24550
  if (event.type === "tool_execution_start")
@@ -24255,7 +24642,8 @@ async function* runChairmanFixLoop(args) {
24255
24642
  toolRegistry: args.config.tools,
24256
24643
  providerStream: args.config.providerStream,
24257
24644
  runMode: "implementation",
24258
- retryPrompt: buildMotionFixPrompt(current)
24645
+ retryPrompt: buildMotionFixPrompt(current),
24646
+ languageModule: args.languageModule
24259
24647
  });
24260
24648
  for await (const event of fixGenerator) {
24261
24649
  if (event.type === "tool_execution_start")
@@ -24400,7 +24788,8 @@ async function* applyRetryIfMissing(args) {
24400
24788
  eventBus: args.config.eventBus,
24401
24789
  toolRegistry: args.config.tools,
24402
24790
  providerStream: args.config.providerStream,
24403
- runMode: args.config.runMode
24791
+ runMode: args.config.runMode,
24792
+ languageModule: args.languageModule
24404
24793
  });
24405
24794
  for await (const event of retryGenerator) {
24406
24795
  if (event.type === "tool_execution_start") {
@@ -24422,6 +24811,7 @@ var init_councilApi = __esm({
24422
24811
  init_toolSchemas();
24423
24812
  init_systemPromptBuilder();
24424
24813
  init_tools();
24814
+ init_languagePolicy();
24425
24815
  init_events();
24426
24816
  init_AgentHarness();
24427
24817
  init_modeBanners();
@@ -33163,12 +33553,32 @@ function finalizeStreamingAssistant(setMessages) {
33163
33553
  });
33164
33554
  }
33165
33555
 
33556
+ // src/cli/utils/envNumber.ts
33557
+ function envNumber(raw, opts) {
33558
+ const { default: def, min, max } = opts;
33559
+ if (raw === void 0 || raw === null) return def;
33560
+ const trimmed = raw.trim();
33561
+ if (trimmed.length === 0) return def;
33562
+ if (trimmed.toLowerCase() === "undefined" || trimmed.toLowerCase() === "null") return def;
33563
+ const parsed = Number.parseInt(trimmed, 10);
33564
+ if (!Number.isFinite(parsed)) return def;
33565
+ const absStr = `${Math.abs(parsed)}`;
33566
+ const body = trimmed.replace(/^[-+]/, "").replace(/^0+(?=\d)/, "");
33567
+ if (body !== absStr) return def;
33568
+ const firstChar = trimmed[0];
33569
+ const expectedSign = parsed < 0 ? "-" : /[0-9+]/.test(firstChar ?? "");
33570
+ const actualSign = parsed < 0 ? firstChar === "-" : firstChar !== "-";
33571
+ if (!expectedSign || !actualSign) return def;
33572
+ let clamped = parsed;
33573
+ if (min !== void 0 && clamped < min) clamped = min;
33574
+ if (max !== void 0 && clamped > max) clamped = max;
33575
+ return clamped;
33576
+ }
33577
+
33166
33578
  // src/cli/hooks/historyCompaction.ts
33167
33579
  var COMPACT_MARKER = "[history] Earlier turns were compacted to stay within the context budget.";
33168
33580
  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;
33581
+ const envTurns = envNumber(process.env.ZELARI_HISTORY_TURNS, { default: 6, min: 0 });
33172
33582
  const turns = opts?.maxMessages ? Math.ceil(opts.maxMessages / 4) : envTurns;
33173
33583
  if (turns <= 0) return 0;
33174
33584
  return turns * 4;
@@ -33379,21 +33789,24 @@ function useChatTurn(params) {
33379
33789
  const workspaceContext = [workspaceSummary, zelariReadHint].filter(Boolean).join("\n\n");
33380
33790
  let systemPrompt;
33381
33791
  try {
33792
+ const languageModule = buildLanguagePolicyModuleFor(userText);
33382
33793
  systemPrompt = buildSystemPrompt(singleAgentRole, {
33383
33794
  tools: getAllTools(),
33384
33795
  toolNames: toolListNames,
33385
33796
  aiConfig: {
33386
33797
  enabledSkills: [],
33387
33798
  enabledTools: toolListNames,
33388
- customPromptModules: [SINGLE_AGENT_IDENTITY_MODULE],
33799
+ customPromptModules: [SINGLE_AGENT_IDENTITY_MODULE, languageModule],
33389
33800
  agentSkillConfigs: []
33390
33801
  },
33391
33802
  workspaceContext: workspaceContext || void 0,
33392
33803
  ragContext: planSummary || void 0
33393
33804
  });
33394
33805
  } catch {
33806
+ const languageModule = buildLanguagePolicyModuleFor(userText);
33395
33807
  systemPrompt = [
33396
33808
  SINGLE_AGENT_IDENTITY_MODULE.content,
33809
+ languageModule.content,
33397
33810
  shellContextBlock,
33398
33811
  "# Available Tools",
33399
33812
  "You can call these tools. Use them to take action and gather information autonomously:",
@@ -33402,16 +33815,14 @@ function useChatTurn(params) {
33402
33815
  ...planSummary ? ["", planSummary] : []
33403
33816
  ].join("\n");
33404
33817
  }
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
- })();
33818
+ const maxToolCallsPerTurn = envNumber(process.env.ZELARI_MAX_TOOL_CALLS, {
33819
+ default: 25,
33820
+ min: 1
33821
+ });
33822
+ const maxToolLoopIterations = envNumber(process.env.ZELARI_MAX_TOOL_LOOP_ITERATIONS, {
33823
+ default: 90,
33824
+ min: 1
33825
+ });
33415
33826
  const harness2 = new AgentHarness({
33416
33827
  model: envConfig.model,
33417
33828
  provider: "openai-compatible",
@@ -33748,11 +34159,10 @@ async function dispatchCouncilPromptImpl(text, deps, overrides = {}) {
33748
34159
  const councilFeedbackStore = new FeedbackStore2();
33749
34160
  let streamContent = "";
33750
34161
  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
- })();
34162
+ const councilMaxToolCalls = envNumber(process.env.ZELARI_MAX_TOOL_CALLS, {
34163
+ default: 15,
34164
+ min: 1
34165
+ });
33756
34166
  let membersCompleted = 0;
33757
34167
  let chairmanProducedOutput = false;
33758
34168
  let chairmanSynthesisText = "";
@@ -34129,11 +34539,10 @@ async function runZelariMissionInTui(userMessage, deps, emit) {
34129
34539
  hasPlan: hasWorkspacePlan2(projectRoot)
34130
34540
  });
34131
34541
  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
- })();
34542
+ const chairmanBudget = envNumber(process.env.ZELARI_MODE_MAX_TOOLS_LUCIFER, {
34543
+ default: 30,
34544
+ min: 1
34545
+ });
34137
34546
  try {
34138
34547
  await runZelariMission2(userMessage, brief, {
34139
34548
  projectRoot,
@@ -37295,6 +37704,7 @@ function emitEvent(event) {
37295
37704
  // src/cli/runHeadless.ts
37296
37705
  init_harness();
37297
37706
  init_toolRegistry();
37707
+ init_skills2();
37298
37708
  async function runHeadless(opts) {
37299
37709
  const { provider, model } = resolveHeadlessProvider(opts);
37300
37710
  const key = await resolveHeadlessKey(provider);
@@ -37322,11 +37732,61 @@ async function runHeadlessSingle(opts, provider, model, providerStream) {
37322
37732
  description: t.function.description,
37323
37733
  parameters: t.function.parameters
37324
37734
  }));
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");
37735
+ const toolNames = tools.map((t) => t.name);
37736
+ let systemPrompt;
37737
+ let languageDirectiveContent;
37738
+ try {
37739
+ languageDirectiveContent = buildLanguagePolicyModuleFor(opts.task).content;
37740
+ } catch {
37741
+ languageDirectiveContent = "# Response Language\nReply in the user's language when possible, otherwise Italian.";
37742
+ }
37743
+ try {
37744
+ const headlessRole = {
37745
+ id: "single",
37746
+ name: "Zelari Code",
37747
+ codename: "zelari",
37748
+ role: "headless coding agent",
37749
+ color: "#00d9a3",
37750
+ avatar: "\u25C6",
37751
+ tools: toolNames,
37752
+ // v1.7.0 fix (agy audit): include platform/shell context in the role
37753
+ // prompt so the agent knows its cwd and shell — previously this was
37754
+ // empty (systemPrompt: '') and the agent lacked the platform block
37755
+ // the TUI single-agent path gets.
37756
+ systemPrompt: [
37757
+ "# Platform",
37758
+ `platform: ${process.platform}`,
37759
+ `shell: ${process.platform === "win32" ? "cmd.exe / Git Bash (auto-detected)" : "/bin/sh"}`,
37760
+ "",
37761
+ "# Working Directory",
37762
+ `You are running in: ${process.cwd()}`,
37763
+ "All relative file paths are resolved against this directory.",
37764
+ "The shell is NON-INTERACTIVE (stdin closed): pass non-interactive flags (--yes, --force, --template)."
37765
+ ].join("\n")
37766
+ };
37767
+ systemPrompt = buildSystemPrompt(headlessRole, {
37768
+ tools: getAllTools(),
37769
+ toolNames,
37770
+ aiConfig: {
37771
+ enabledSkills: [],
37772
+ enabledTools: toolNames,
37773
+ customPromptModules: [SINGLE_AGENT_IDENTITY_MODULE, {
37774
+ type: "language-policy",
37775
+ title: "Response Language",
37776
+ priority: 5,
37777
+ content: languageDirectiveContent
37778
+ }],
37779
+ agentSkillConfigs: []
37780
+ }
37781
+ });
37782
+ } catch {
37783
+ systemPrompt = [
37784
+ "You are zelari-code, a CLI coding agent. Be concise and direct.",
37785
+ "When the user asks you to write code, debug, or explore, be proactive: list files and read key files to understand the project.",
37786
+ "When you finish a task, briefly summarize what you did.",
37787
+ languageDirectiveContent
37788
+ ].join("\n");
37789
+ }
37330
37790
  const harness = new AgentHarness({
37331
37791
  model,
37332
37792
  provider,
@@ -37339,9 +37799,8 @@ async function runHeadlessSingle(opts, provider, model, providerStream) {
37339
37799
  toolRegistry,
37340
37800
  providerStream,
37341
37801
  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;
37802
+ const n = envNumber(process.env.ZELARI_MAX_TOOL_LOOP_ITERATIONS, { default: 30, min: 1 });
37803
+ return n;
37345
37804
  })()
37346
37805
  });
37347
37806
  let finalReason = "completed";