zelari-code 1.5.5 → 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
 
@@ -18592,13 +18959,33 @@ var init_AgentHarness = __esm({
18592
18959
  this.maxQueuedIterations = config2.maxQueuedIterations ?? 3;
18593
18960
  this.maxToolLoopIterations = config2.maxToolLoopIterations ?? 30;
18594
18961
  }
18962
+ /**
18963
+ * Snapshot of the live transcript (`this.config.messages`) as mutated
18964
+ * across `run()` iterations — the seed passed at construction plus any
18965
+ * assistant turns (with `toolCalls`) and tool results the loop appended.
18966
+ *
18967
+ * Used by the single-agent chat loop to carry rolling history across
18968
+ * turns: after a run completes, the caller reads the tail of this array
18969
+ * (the assistant/tool messages produced this turn) and feeds it back as
18970
+ * the seed for the next turn, so the model sees its own prior question
18971
+ * when the user answers with a short reply.
18972
+ *
18973
+ * The returned reference is the live array — callers MUST treat it as
18974
+ * read-only (do not mutate). Copy (`[...harness.getMessages()]`) before
18975
+ * retaining across runs.
18976
+ *
18977
+ * @since v1.6.0
18978
+ */
18979
+ getMessages() {
18980
+ return this.config.messages;
18981
+ }
18595
18982
  /**
18596
18983
  * Member identity fields (memberId + memberName) to merge into every
18597
18984
  * event payload. Returns an empty object when the run is a direct
18598
18985
  * user prompt (no member context), so call sites can spread it
18599
18986
  * unconditionally.
18600
18987
  *
18601
- * @since 0.5.0
18988
+ * @since v0.5.0
18602
18989
  */
18603
18990
  memberFields() {
18604
18991
  return {
@@ -23418,6 +23805,17 @@ async function* runCouncilPure(userMessage, config2, callbacks = {}) {
23418
23805
  const sessionId = config2.sessionId ?? crypto.randomUUID();
23419
23806
  const runMode = config2.runMode ?? "implementation";
23420
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
+ }
23421
23819
  yield createBrainEvent("council_mode", sessionId, {
23422
23820
  tier: councilTierFromSize(config2.councilSize),
23423
23821
  councilSize: config2.councilSize,
@@ -23541,7 +23939,8 @@ async function* runCouncilPure(userMessage, config2, callbacks = {}) {
23541
23939
  effectiveModel,
23542
23940
  onToolCall: () => {
23543
23941
  toolCalls += 1;
23544
- }
23942
+ },
23943
+ languageModule: councilLanguageModule
23545
23944
  });
23546
23945
  } else if (isDesignPhase) {
23547
23946
  enforceDesignPhaseToolEmissions(agent.id, emittedToolNames);
@@ -23679,7 +24078,8 @@ async function* runCouncilPure(userMessage, config2, callbacks = {}) {
23679
24078
  effectiveModel,
23680
24079
  onToolCall: () => {
23681
24080
  toolCalls += 1;
23682
- }
24081
+ },
24082
+ languageModule: councilLanguageModule
23683
24083
  });
23684
24084
  } else if (isDesignPhase) {
23685
24085
  enforceDesignPhaseToolEmissions(oracle.id, emittedToolNames);
@@ -23847,7 +24247,8 @@ async function* runCouncilPure(userMessage, config2, callbacks = {}) {
23847
24247
  effectiveModel,
23848
24248
  onToolCall: () => {
23849
24249
  toolCalls += 1;
23850
- }
24250
+ },
24251
+ languageModule: councilLanguageModule
23851
24252
  });
23852
24253
  } else if (isDesignPhase) {
23853
24254
  enforceDesignPhaseToolEmissions(chairman.id, emittedToolNames);
@@ -23907,7 +24308,8 @@ async function* runCouncilPure(userMessage, config2, callbacks = {}) {
23907
24308
  onSuccessfulWrite: () => {
23908
24309
  successfulWriteCount += 1;
23909
24310
  },
23910
- onCouncilStatus: callbacks.onCouncilStatus
24311
+ onCouncilStatus: callbacks.onCouncilStatus,
24312
+ languageModule: councilLanguageModule
23911
24313
  });
23912
24314
  }
23913
24315
  }
@@ -23926,7 +24328,8 @@ async function* runCouncilPure(userMessage, config2, callbacks = {}) {
23926
24328
  effectiveModel,
23927
24329
  onToolCall: () => {
23928
24330
  toolCalls += 1;
23929
- }
24331
+ },
24332
+ languageModule: councilLanguageModule
23930
24333
  });
23931
24334
  }
23932
24335
  if (chairmanProjectRoot) {
@@ -23944,7 +24347,8 @@ async function* runCouncilPure(userMessage, config2, callbacks = {}) {
23944
24347
  onToolCall: () => {
23945
24348
  toolCalls += 1;
23946
24349
  },
23947
- onCouncilStatus: callbacks.onCouncilStatus
24350
+ onCouncilStatus: callbacks.onCouncilStatus,
24351
+ languageModule: councilLanguageModule
23948
24352
  });
23949
24353
  }
23950
24354
  }
@@ -24010,7 +24414,8 @@ async function* applyCompletionRetry(args) {
24010
24414
  toolRegistry: args.config.tools,
24011
24415
  providerStream: args.config.providerStream,
24012
24416
  runMode: args.config.runMode,
24013
- retryPrompt: buildImplementationVerifyRetryPrompt(retryTool)
24417
+ retryPrompt: buildImplementationVerifyRetryPrompt(retryTool),
24418
+ languageModule: args.languageModule
24014
24419
  });
24015
24420
  for await (const event of retryGenerator) {
24016
24421
  if (event.type === "tool_execution_start") {
@@ -24068,7 +24473,8 @@ async function* applyImplementationWriteRetry(args) {
24068
24473
  toolRegistry: args.config.tools,
24069
24474
  providerStream: args.config.providerStream,
24070
24475
  runMode: "implementation",
24071
- retryPrompt: buildImplementationWriteRetryPrompt(args.userMessage)
24476
+ retryPrompt: buildImplementationWriteRetryPrompt(args.userMessage),
24477
+ languageModule: args.languageModule
24072
24478
  });
24073
24479
  for await (const event of retryGenerator) {
24074
24480
  if (event.type === "tool_execution_start")
@@ -24137,7 +24543,8 @@ async function* runChairmanDeliveryLoop(args) {
24137
24543
  toolRegistry: args.config.tools,
24138
24544
  providerStream: args.config.providerStream,
24139
24545
  runMode: "implementation",
24140
- retryPrompt: buildDeliveryFixPrompt(blocking, args.userMessage)
24546
+ retryPrompt: buildDeliveryFixPrompt(blocking, args.userMessage),
24547
+ languageModule: args.languageModule
24141
24548
  });
24142
24549
  for await (const event of fixGenerator) {
24143
24550
  if (event.type === "tool_execution_start")
@@ -24235,7 +24642,8 @@ async function* runChairmanFixLoop(args) {
24235
24642
  toolRegistry: args.config.tools,
24236
24643
  providerStream: args.config.providerStream,
24237
24644
  runMode: "implementation",
24238
- retryPrompt: buildMotionFixPrompt(current)
24645
+ retryPrompt: buildMotionFixPrompt(current),
24646
+ languageModule: args.languageModule
24239
24647
  });
24240
24648
  for await (const event of fixGenerator) {
24241
24649
  if (event.type === "tool_execution_start")
@@ -24380,7 +24788,8 @@ async function* applyRetryIfMissing(args) {
24380
24788
  eventBus: args.config.eventBus,
24381
24789
  toolRegistry: args.config.tools,
24382
24790
  providerStream: args.config.providerStream,
24383
- runMode: args.config.runMode
24791
+ runMode: args.config.runMode,
24792
+ languageModule: args.languageModule
24384
24793
  });
24385
24794
  for await (const event of retryGenerator) {
24386
24795
  if (event.type === "tool_execution_start") {
@@ -24402,6 +24811,7 @@ var init_councilApi = __esm({
24402
24811
  init_toolSchemas();
24403
24812
  init_systemPromptBuilder();
24404
24813
  init_tools();
24814
+ init_languagePolicy();
24405
24815
  init_events();
24406
24816
  init_AgentHarness();
24407
24817
  init_modeBanners();
@@ -33050,6 +33460,14 @@ init_keyStore();
33050
33460
  init_toolRegistry();
33051
33461
  init_skills2();
33052
33462
 
33463
+ // packages/core/dist/events/index.js
33464
+ init_events();
33465
+
33466
+ // packages/core/dist/index.js
33467
+ init_harness();
33468
+ init_council();
33469
+ init_skills2();
33470
+
33053
33471
  // src/cli/hooks/messageHelpers.ts
33054
33472
  function appendSystem(setMessages, content, ts = Date.now()) {
33055
33473
  setMessages((prev2) => [
@@ -33135,6 +33553,81 @@ function finalizeStreamingAssistant(setMessages) {
33135
33553
  });
33136
33554
  }
33137
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
+
33578
+ // src/cli/hooks/historyCompaction.ts
33579
+ var COMPACT_MARKER = "[history] Earlier turns were compacted to stay within the context budget.";
33580
+ function resolveMaxMessages(opts) {
33581
+ const envTurns = envNumber(process.env.ZELARI_HISTORY_TURNS, { default: 6, min: 0 });
33582
+ const turns = opts?.maxMessages ? Math.ceil(opts.maxMessages / 4) : envTurns;
33583
+ if (turns <= 0) return 0;
33584
+ return turns * 4;
33585
+ }
33586
+ function findValidCutIndex(messages, naiveCut) {
33587
+ let cut = naiveCut;
33588
+ while (cut < messages.length) {
33589
+ const kept = messages.slice(cut);
33590
+ const declared = /* @__PURE__ */ new Set();
33591
+ for (const m of kept) {
33592
+ if (m.role === "assistant" && m.toolCalls) {
33593
+ for (const tc of m.toolCalls) declared.add(tc.id);
33594
+ }
33595
+ }
33596
+ let moved = false;
33597
+ for (let k = 0; k < kept.length; k++) {
33598
+ const m = kept[k];
33599
+ if (m.role === "tool" && m.toolCallId && !declared.has(m.toolCallId)) {
33600
+ for (let j = cut - 1; j >= 0; j--) {
33601
+ const prev2 = messages[j];
33602
+ if (prev2.role === "assistant" && prev2.toolCalls && prev2.toolCalls.some((tc) => tc.id === m.toolCallId)) {
33603
+ cut = j;
33604
+ moved = true;
33605
+ break;
33606
+ }
33607
+ }
33608
+ break;
33609
+ }
33610
+ }
33611
+ if (!moved) break;
33612
+ }
33613
+ return cut;
33614
+ }
33615
+ function compactHistory(messages, opts) {
33616
+ const maxMessages = resolveMaxMessages(opts);
33617
+ if (maxMessages === 0) return [];
33618
+ if (messages.length <= maxMessages * 2) return messages;
33619
+ const naiveCut = messages.length - maxMessages;
33620
+ const cut = findValidCutIndex(messages, naiveCut);
33621
+ const dropped = cut;
33622
+ if (dropped === 0) return messages;
33623
+ const kept = messages.slice(cut);
33624
+ const summary = {
33625
+ role: "system",
33626
+ content: `${COMPACT_MARKER} ${dropped} earlier message(s) dropped.`
33627
+ };
33628
+ return [summary, ...kept];
33629
+ }
33630
+
33138
33631
  // src/cli/hooks/chatStats.ts
33139
33632
  function computeSessionStatsDelta(realUsage, userText, assistantContent, model, prev2) {
33140
33633
  const promptTokens = realUsage ? realUsage.promptTokens : Math.ceil(userText.length / 4);
@@ -33160,16 +33653,22 @@ function useChatTurn(params) {
33160
33653
  setSessionActive,
33161
33654
  setSessionStats,
33162
33655
  setLive,
33163
- liveRef
33656
+ liveRef,
33657
+ setPicker
33164
33658
  } = params;
33165
33659
  const harnessRef = useRef4(null);
33166
33660
  const [queueCount, setQueueCount] = useState6(0);
33661
+ const historyRef = useRef4([]);
33167
33662
  const useLiveModel = !!(setLive && liveRef);
33168
33663
  const dispatchPrompt = useCallback2(
33169
33664
  async (userText, opts) => {
33170
33665
  let envConfig;
33171
33666
  let harness;
33667
+ let historySeedLen = 0;
33668
+ let turnSucceeded = false;
33172
33669
  try {
33670
+ historyRef.current = compactHistory(historyRef.current);
33671
+ historySeedLen = historyRef.current.length;
33173
33672
  envConfig = await providerFromEnv();
33174
33673
  if (!envConfig) {
33175
33674
  const active = resolveActiveProvider();
@@ -33290,21 +33789,24 @@ function useChatTurn(params) {
33290
33789
  const workspaceContext = [workspaceSummary, zelariReadHint].filter(Boolean).join("\n\n");
33291
33790
  let systemPrompt;
33292
33791
  try {
33792
+ const languageModule = buildLanguagePolicyModuleFor(userText);
33293
33793
  systemPrompt = buildSystemPrompt(singleAgentRole, {
33294
33794
  tools: getAllTools(),
33295
33795
  toolNames: toolListNames,
33296
33796
  aiConfig: {
33297
33797
  enabledSkills: [],
33298
33798
  enabledTools: toolListNames,
33299
- customPromptModules: [SINGLE_AGENT_IDENTITY_MODULE],
33799
+ customPromptModules: [SINGLE_AGENT_IDENTITY_MODULE, languageModule],
33300
33800
  agentSkillConfigs: []
33301
33801
  },
33302
33802
  workspaceContext: workspaceContext || void 0,
33303
33803
  ragContext: planSummary || void 0
33304
33804
  });
33305
33805
  } catch {
33806
+ const languageModule = buildLanguagePolicyModuleFor(userText);
33306
33807
  systemPrompt = [
33307
33808
  SINGLE_AGENT_IDENTITY_MODULE.content,
33809
+ languageModule.content,
33308
33810
  shellContextBlock,
33309
33811
  "# Available Tools",
33310
33812
  "You can call these tools. Use them to take action and gather information autonomously:",
@@ -33313,21 +33815,23 @@ function useChatTurn(params) {
33313
33815
  ...planSummary ? ["", planSummary] : []
33314
33816
  ].join("\n");
33315
33817
  }
33316
- const maxToolCallsPerTurn = (() => {
33317
- const raw = process.env.ZELARI_MAX_TOOL_CALLS;
33318
- const n = raw ? Number.parseInt(raw, 10) : 25;
33319
- return Number.isFinite(n) && n > 0 ? n : 25;
33320
- })();
33321
- const maxToolLoopIterations = (() => {
33322
- const raw = process.env.ZELARI_MAX_TOOL_LOOP_ITERATIONS;
33323
- const n = raw ? Number.parseInt(raw, 10) : 90;
33324
- return Number.isFinite(n) && n > 0 ? n : 90;
33325
- })();
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
+ });
33326
33826
  const harness2 = new AgentHarness({
33327
33827
  model: envConfig.model,
33328
33828
  provider: "openai-compatible",
33329
33829
  messages: [
33330
33830
  { role: "system", content: systemPrompt },
33831
+ // v1.6.0: seed prior turns so the model sees its own last
33832
+ // question when the user answers briefly. historyRef.current
33833
+ // is compacted above (possibly empty if ZELARI_HISTORY_TURNS=0).
33834
+ ...historyRef.current,
33331
33835
  { role: "user", content: userText }
33332
33836
  ],
33333
33837
  tools: toolRegistry.toOpenAITools().map((t) => ({
@@ -33458,10 +33962,53 @@ function useChatTurn(params) {
33458
33962
  }
33459
33963
  }
33460
33964
  }
33965
+ turnSucceeded = true;
33461
33966
  } finally {
33462
33967
  flushStreaming();
33463
33968
  if (useLiveModel) finalizeStreaming(setMessages, setLive);
33464
33969
  else finalizeStreamingAssistant(setMessages);
33970
+ try {
33971
+ const h = harnessRef.current;
33972
+ if (h && turnSucceeded) {
33973
+ const all = h.getMessages();
33974
+ const seedLen = 1 + historySeedLen + 1;
33975
+ if (all.length > seedLen) {
33976
+ historyRef.current = historyRef.current.concat(
33977
+ all.slice(seedLen)
33978
+ );
33979
+ }
33980
+ }
33981
+ } catch {
33982
+ }
33983
+ if (turnSucceeded && setPicker && assistantContent) {
33984
+ try {
33985
+ const clar = parseClarificationRequest(assistantContent);
33986
+ if (clar && clar.choices && clar.choices.length >= 2) {
33987
+ const cleaned = cleanAgentContent(assistantContent);
33988
+ if (cleaned !== assistantContent) {
33989
+ setMessages((prev2) => {
33990
+ const next = [...prev2];
33991
+ for (let i = next.length - 1; i >= 0; i--) {
33992
+ if (next[i].role === "assistant") {
33993
+ next[i] = { ...next[i], content: cleaned };
33994
+ break;
33995
+ }
33996
+ }
33997
+ return next;
33998
+ });
33999
+ }
34000
+ setPicker({
34001
+ kind: "clarification",
34002
+ title: clar.question,
34003
+ items: clar.choices.map((c) => ({ value: c, label: c })),
34004
+ onAnswer: (value) => {
34005
+ void dispatchPrompt(value);
34006
+ }
34007
+ });
34008
+ }
34009
+ } catch {
34010
+ }
34011
+ }
33465
34012
  harnessRef.current = null;
33466
34013
  setQueueCount(0);
33467
34014
  setBusy(false);
@@ -33612,11 +34159,10 @@ async function dispatchCouncilPromptImpl(text, deps, overrides = {}) {
33612
34159
  const councilFeedbackStore = new FeedbackStore2();
33613
34160
  let streamContent = "";
33614
34161
  let streamMemberId = null;
33615
- const councilMaxToolCalls = (() => {
33616
- const raw = process.env.ZELARI_MAX_TOOL_CALLS;
33617
- const n = raw ? Number.parseInt(raw, 10) : 15;
33618
- return Number.isFinite(n) && n > 0 ? n : 15;
33619
- })();
34162
+ const councilMaxToolCalls = envNumber(process.env.ZELARI_MAX_TOOL_CALLS, {
34163
+ default: 15,
34164
+ min: 1
34165
+ });
33620
34166
  let membersCompleted = 0;
33621
34167
  let chairmanProducedOutput = false;
33622
34168
  let chairmanSynthesisText = "";
@@ -33993,11 +34539,10 @@ async function runZelariMissionInTui(userMessage, deps, emit) {
33993
34539
  hasPlan: hasWorkspacePlan2(projectRoot)
33994
34540
  });
33995
34541
  const memory = await getMemoryBackend2(projectRoot);
33996
- const chairmanBudget = (() => {
33997
- const raw = process.env.ZELARI_MODE_MAX_TOOLS_LUCIFER;
33998
- const n = raw ? Number.parseInt(raw, 10) : 30;
33999
- return Number.isFinite(n) && n > 0 ? n : 30;
34000
- })();
34542
+ const chairmanBudget = envNumber(process.env.ZELARI_MODE_MAX_TOOLS_LUCIFER, {
34543
+ default: 30,
34544
+ min: 1
34545
+ });
34001
34546
  try {
34002
34547
  await runZelariMission2(userMessage, brief, {
34003
34548
  projectRoot,
@@ -36404,7 +36949,11 @@ function App() {
36404
36949
  setSessionActive: session.setSessionActive,
36405
36950
  setSessionStats,
36406
36951
  setLive: session.setLive,
36407
- liveRef: session.liveRef
36952
+ liveRef: session.liveRef,
36953
+ // v1.6.0: lets dispatchPrompt open a SelectList when the agent poses a
36954
+ // ---QUESTION--- clarifying block, so the user picks from the offered
36955
+ // choices instead of typing (and the answer binds via rolling history).
36956
+ setPicker
36408
36957
  });
36409
36958
  const onNewSession = useCallback5((id) => {
36410
36959
  void session.writerRef.current?.close();
@@ -36445,8 +36994,12 @@ function App() {
36445
36994
  });
36446
36995
  const onPickerSelect = useCallback5((value) => {
36447
36996
  if (!picker) return;
36448
- const cmd = `${picker.commandPrefix} ${value}`;
36449
36997
  setPicker(null);
36998
+ if (picker.kind === "clarification") {
36999
+ picker.onAnswer?.(value);
37000
+ return;
37001
+ }
37002
+ const cmd = `${picker.commandPrefix} ${value}`;
36450
37003
  void handleSubmit(cmd);
36451
37004
  }, [picker, handleSubmit]);
36452
37005
  const onPickerCancel = useCallback5(() => setPicker(null), []);
@@ -37151,6 +37704,7 @@ function emitEvent(event) {
37151
37704
  // src/cli/runHeadless.ts
37152
37705
  init_harness();
37153
37706
  init_toolRegistry();
37707
+ init_skills2();
37154
37708
  async function runHeadless(opts) {
37155
37709
  const { provider, model } = resolveHeadlessProvider(opts);
37156
37710
  const key = await resolveHeadlessKey(provider);
@@ -37178,11 +37732,61 @@ async function runHeadlessSingle(opts, provider, model, providerStream) {
37178
37732
  description: t.function.description,
37179
37733
  parameters: t.function.parameters
37180
37734
  }));
37181
- const systemPrompt = [
37182
- "You are zelari-code, a CLI coding agent. Be concise and direct.",
37183
- "When the user asks you to write code, debug, or explore, be proactive: list files and read key files to understand the project.",
37184
- "When you finish a task, briefly summarize what you did."
37185
- ].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
+ }
37186
37790
  const harness = new AgentHarness({
37187
37791
  model,
37188
37792
  provider,
@@ -37195,9 +37799,8 @@ async function runHeadlessSingle(opts, provider, model, providerStream) {
37195
37799
  toolRegistry,
37196
37800
  providerStream,
37197
37801
  maxToolLoopIterations: (() => {
37198
- const raw = process.env.ZELARI_MAX_TOOL_LOOP_ITERATIONS;
37199
- const n = raw ? Number.parseInt(raw, 10) : 30;
37200
- 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;
37201
37804
  })()
37202
37805
  });
37203
37806
  let finalReason = "completed";