@nathapp/nax 0.79.1 → 0.79.2

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.
Files changed (2) hide show
  1. package/dist/nax.js +240 -172
  2. package/package.json +1 -1
package/dist/nax.js CHANGED
@@ -18733,6 +18733,181 @@ var init_json_file = __esm(() => {
18733
18733
  init_logger2();
18734
18734
  });
18735
18735
 
18736
+ // src/config/migrations.ts
18737
+ function migrateLegacyTestPattern(raw, logger) {
18738
+ const context = raw.context;
18739
+ const legacyPattern = context?.testCoverage?.testPattern;
18740
+ if (legacyPattern === undefined)
18741
+ return raw;
18742
+ logger?.warn("config", "context.testCoverage.testPattern is deprecated \u2014 migrate to " + "execution.smartTestRunner.testFilePatterns (array). Migration shim applied.", { legacyPattern });
18743
+ const safeContext = context ?? {};
18744
+ const { testPattern: _drop, ...testCoverageRest } = safeContext.testCoverage ?? {};
18745
+ const migratedContext = { ...safeContext, testCoverage: testCoverageRest };
18746
+ const execution = raw.execution;
18747
+ const smartRunnerPatterns = execution?.smartTestRunner?.testFilePatterns;
18748
+ if (smartRunnerPatterns !== undefined) {
18749
+ return { ...raw, context: migratedContext };
18750
+ }
18751
+ const aliasedSmartRunner = {
18752
+ ...execution?.smartTestRunner,
18753
+ testFilePatterns: [legacyPattern]
18754
+ };
18755
+ const migratedExecution = {
18756
+ ...execution,
18757
+ smartTestRunner: aliasedSmartRunner
18758
+ };
18759
+ return { ...raw, execution: migratedExecution, context: migratedContext };
18760
+ }
18761
+ function migrateLegacyReviewModelKey(raw, logger) {
18762
+ const review = raw.review;
18763
+ if (!review)
18764
+ return raw;
18765
+ const semantic = migrateBlock(review.semantic, "review.semantic", logger);
18766
+ const adversarial = migrateBlock(review.adversarial, "review.adversarial", logger);
18767
+ if (semantic === review.semantic && adversarial === review.adversarial)
18768
+ return raw;
18769
+ return {
18770
+ ...raw,
18771
+ review: {
18772
+ ...review,
18773
+ ...semantic !== undefined ? { semantic } : {},
18774
+ ...adversarial !== undefined ? { adversarial } : {}
18775
+ }
18776
+ };
18777
+ function migrateBlock(block, path, log) {
18778
+ if (!block || block.modelTier === undefined)
18779
+ return block;
18780
+ const { modelTier, ...rest } = block;
18781
+ if (block.model !== undefined) {
18782
+ log?.warn("config", `${path}.modelTier is deprecated and ignored \u2014 ${path}.model is set and wins. Remove ${path}.modelTier.`, { legacyKey: `${path}.modelTier`, canonicalKey: `${path}.model` });
18783
+ return rest;
18784
+ }
18785
+ log?.warn("config", `${path}.modelTier is deprecated \u2014 migrate to ${path}.model (accepts the same tier string or a { agent, model } pin). Migration shim applied.`, { legacyKey: `${path}.modelTier`, canonicalKey: `${path}.model`, value: modelTier });
18786
+ return { ...rest, model: modelTier };
18787
+ }
18788
+ }
18789
+
18790
+ // src/config/compat-shims.ts
18791
+ function defaultConfigWarn(msg) {
18792
+ try {
18793
+ getLogger().warn("config", msg);
18794
+ } catch {}
18795
+ }
18796
+ function createConfigWarnDedupe(sink = defaultConfigWarn) {
18797
+ const seen = new Set;
18798
+ const admit = (msg, data) => {
18799
+ const key = data === undefined ? msg : `${msg}\x00${JSON.stringify(data)}`;
18800
+ if (seen.has(key))
18801
+ return false;
18802
+ seen.add(key);
18803
+ return true;
18804
+ };
18805
+ return {
18806
+ warn: (msg) => {
18807
+ if (admit(msg))
18808
+ sink(msg);
18809
+ },
18810
+ wrapLogger: (logger) => logger && {
18811
+ warn: (stage, message, data) => {
18812
+ if (admit(message, data))
18813
+ logger.warn(stage, message, data);
18814
+ }
18815
+ }
18816
+ };
18817
+ }
18818
+ function applyRemovedStrategyCompat(conf, warn = defaultConfigWarn) {
18819
+ const routing = conf.routing;
18820
+ const strategy = routing?.strategy;
18821
+ const REMOVED_STRATEGIES = ["manual", "adaptive", "custom"];
18822
+ if (typeof strategy === "string" && REMOVED_STRATEGIES.includes(strategy)) {
18823
+ warn(`routing.strategy="${strategy}" was removed in ROUTE-001 and is no longer supported. Falling back to "keyword". Update your config to use "keyword" or "llm".`);
18824
+ return { ...conf, routing: { ...routing, strategy: "keyword" } };
18825
+ }
18826
+ return conf;
18827
+ }
18828
+ function _applyRemovedRoutingKeysShim(conf, warn = defaultConfigWarn) {
18829
+ const routing = conf.routing;
18830
+ if (!routing || typeof routing !== "object")
18831
+ return conf;
18832
+ const REMOVED_ROUTING_KEYS = ["customStrategyPath", "adaptive"];
18833
+ let newRouting = routing;
18834
+ for (const key of REMOVED_ROUTING_KEYS) {
18835
+ if (key in newRouting) {
18836
+ warn(`routing.${key} was removed in ROUTE-001 along with the "custom"/"adaptive" strategies and has no effect. Remove it from your config.`);
18837
+ const { [key]: _removed, ...rest } = newRouting;
18838
+ newRouting = rest;
18839
+ }
18840
+ }
18841
+ return newRouting === routing ? conf : { ...conf, routing: newRouting };
18842
+ }
18843
+ function applyBatchModeCompat(conf, warn = defaultConfigWarn) {
18844
+ const routing = conf.routing;
18845
+ const llm = routing?.llm;
18846
+ if (llm && "batchMode" in llm && !("mode" in llm)) {
18847
+ const batchMode = llm.batchMode;
18848
+ if (typeof batchMode === "boolean") {
18849
+ const mappedMode = batchMode ? "one-shot" : "per-story";
18850
+ warn(`routing.llm.batchMode is deprecated and will be removed in v1.0. Mapped to mode="${mappedMode}". Update your config to use routing.llm.mode instead.`);
18851
+ return {
18852
+ ...conf,
18853
+ routing: {
18854
+ ...routing,
18855
+ llm: { ...llm, mode: mappedMode }
18856
+ }
18857
+ };
18858
+ }
18859
+ }
18860
+ return conf;
18861
+ }
18862
+ function _applyLegacyReviewExecutionShim(conf, warn = defaultConfigWarn) {
18863
+ let result = conf;
18864
+ const execution = conf.execution;
18865
+ if (execution && typeof execution === "object" && "inlineReview" in execution) {
18866
+ warn("execution.inlineReview is a legacy field that has been removed. Remove it from your config.");
18867
+ const { inlineReview: _ir, ...restExecution } = execution;
18868
+ result = { ...result, execution: restExecution };
18869
+ }
18870
+ const review = result.review ?? conf.review;
18871
+ if (review && typeof review === "object") {
18872
+ let newReview = review;
18873
+ const LEGACY_PLUGIN_MODE_VALUE = "per-story";
18874
+ if ("pluginMode" in review && review.pluginMode === LEGACY_PLUGIN_MODE_VALUE) {
18875
+ warn('review.pluginMode: "per-story" is a legacy value that has been removed. Remove it from your config (or set to "observational"/"gating").');
18876
+ const { pluginMode: _pm, ...rest } = review;
18877
+ newReview = rest;
18878
+ }
18879
+ const dialogue = newReview.dialogue;
18880
+ if (dialogue && typeof dialogue === "object" && dialogue.enabled === true) {
18881
+ warn("review.dialogue.enabled is a legacy field that has been removed. Remove it from your config.");
18882
+ const { dialogue: _d, ...rest } = newReview;
18883
+ newReview = rest;
18884
+ }
18885
+ result = { ...result, review: newReview };
18886
+ }
18887
+ return result;
18888
+ }
18889
+ function applyRoutingRetryDeprecationWarning(conf, warn = defaultConfigWarn) {
18890
+ const routing = conf.routing;
18891
+ const llm = routing?.llm;
18892
+ if (!llm)
18893
+ return conf;
18894
+ if ("retries" in llm) {
18895
+ warn("routing.llm.retries is deprecated (issue #856). " + "This value is still applied but will be removed in v1.0. " + "Retry policy is now declared on each operation \u2014 remove this key from your config.");
18896
+ }
18897
+ if ("retryDelayMs" in llm) {
18898
+ warn("routing.llm.retryDelayMs is deprecated (issue #856). " + "This value is still applied but will be removed in v1.0. " + "Retry policy is now declared on each operation \u2014 remove this key from your config.");
18899
+ }
18900
+ return conf;
18901
+ }
18902
+ function applyConfigCompatShims(conf, logger, dedupe) {
18903
+ const log = dedupe.wrapLogger(logger);
18904
+ const warn = dedupe.warn;
18905
+ return _applyLegacyReviewExecutionShim(_applyRemovedRoutingKeysShim(applyRoutingRetryDeprecationWarning(applyBatchModeCompat(applyRemovedStrategyCompat(migrateLegacyReviewModelKey(migrateLegacyTestPattern(conf, log), log), warn), warn), warn), warn), warn);
18906
+ }
18907
+ var init_compat_shims = __esm(() => {
18908
+ init_logger2();
18909
+ });
18910
+
18736
18911
  // src/config/config-guards.ts
18737
18912
  function rejectLegacyAgentKeys(conf) {
18738
18913
  const legacyKeys = [];
@@ -19140,60 +19315,6 @@ function isPlainObject2(value) {
19140
19315
  return typeof value === "object" && value !== null && !Array.isArray(value) && value.constructor === Object;
19141
19316
  }
19142
19317
 
19143
- // src/config/migrations.ts
19144
- function migrateLegacyTestPattern(raw, logger) {
19145
- const context = raw.context;
19146
- const legacyPattern = context?.testCoverage?.testPattern;
19147
- if (legacyPattern === undefined)
19148
- return raw;
19149
- logger?.warn("config", "context.testCoverage.testPattern is deprecated \u2014 migrate to " + "execution.smartTestRunner.testFilePatterns (array). Migration shim applied.", { legacyPattern });
19150
- const safeContext = context ?? {};
19151
- const { testPattern: _drop, ...testCoverageRest } = safeContext.testCoverage ?? {};
19152
- const migratedContext = { ...safeContext, testCoverage: testCoverageRest };
19153
- const execution = raw.execution;
19154
- const smartRunnerPatterns = execution?.smartTestRunner?.testFilePatterns;
19155
- if (smartRunnerPatterns !== undefined) {
19156
- return { ...raw, context: migratedContext };
19157
- }
19158
- const aliasedSmartRunner = {
19159
- ...execution?.smartTestRunner,
19160
- testFilePatterns: [legacyPattern]
19161
- };
19162
- const migratedExecution = {
19163
- ...execution,
19164
- smartTestRunner: aliasedSmartRunner
19165
- };
19166
- return { ...raw, execution: migratedExecution, context: migratedContext };
19167
- }
19168
- function migrateLegacyReviewModelKey(raw, logger) {
19169
- const review = raw.review;
19170
- if (!review)
19171
- return raw;
19172
- const semantic = migrateBlock(review.semantic, "review.semantic", logger);
19173
- const adversarial = migrateBlock(review.adversarial, "review.adversarial", logger);
19174
- if (semantic === review.semantic && adversarial === review.adversarial)
19175
- return raw;
19176
- return {
19177
- ...raw,
19178
- review: {
19179
- ...review,
19180
- ...semantic !== undefined ? { semantic } : {},
19181
- ...adversarial !== undefined ? { adversarial } : {}
19182
- }
19183
- };
19184
- function migrateBlock(block, path, log) {
19185
- if (!block || block.modelTier === undefined)
19186
- return block;
19187
- const { modelTier, ...rest } = block;
19188
- if (block.model !== undefined) {
19189
- log?.warn("config", `${path}.modelTier is deprecated and ignored \u2014 ${path}.model is set and wins. Remove ${path}.modelTier.`, { legacyKey: `${path}.modelTier`, canonicalKey: `${path}.model` });
19190
- return rest;
19191
- }
19192
- log?.warn("config", `${path}.modelTier is deprecated \u2014 migrate to ${path}.model (accepts the same tier string or a { agent, model } pin). Migration shim applied.`, { legacyKey: `${path}.modelTier`, canonicalKey: `${path}.model`, value: modelTier });
19193
- return { ...rest, model: modelTier };
19194
- }
19195
- }
19196
-
19197
19318
  // src/config/path-security.ts
19198
19319
  import { existsSync as existsSync2, lstatSync, realpathSync } from "fs";
19199
19320
  import { basename, isAbsolute, normalize, resolve } from "path";
@@ -19432,100 +19553,9 @@ function findProjectDir(startDir = process.cwd()) {
19432
19553
  }
19433
19554
  return null;
19434
19555
  }
19435
- function defaultConfigWarn(msg) {
19436
- try {
19437
- getLogger().warn("config", msg);
19438
- } catch {}
19439
- }
19440
- function applyRemovedStrategyCompat(conf) {
19441
- const routing = conf.routing;
19442
- const strategy = routing?.strategy;
19443
- const REMOVED_STRATEGIES = ["manual", "adaptive", "custom"];
19444
- if (typeof strategy === "string" && REMOVED_STRATEGIES.includes(strategy)) {
19445
- defaultConfigWarn(`routing.strategy="${strategy}" was removed in ROUTE-001 and is no longer supported. Falling back to "keyword". Update your config to use "keyword" or "llm".`);
19446
- return { ...conf, routing: { ...routing, strategy: "keyword" } };
19447
- }
19448
- return conf;
19449
- }
19450
- function _applyRemovedRoutingKeysShim(conf, warn = defaultConfigWarn) {
19451
- const routing = conf.routing;
19452
- if (!routing || typeof routing !== "object")
19453
- return conf;
19454
- const REMOVED_ROUTING_KEYS = ["customStrategyPath", "adaptive"];
19455
- let newRouting = routing;
19456
- for (const key of REMOVED_ROUTING_KEYS) {
19457
- if (key in newRouting) {
19458
- warn(`routing.${key} was removed in ROUTE-001 along with the "custom"/"adaptive" strategies and has no effect. Remove it from your config.`);
19459
- const { [key]: _removed, ...rest } = newRouting;
19460
- newRouting = rest;
19461
- }
19462
- }
19463
- return newRouting === routing ? conf : { ...conf, routing: newRouting };
19464
- }
19465
- function applyBatchModeCompat(conf) {
19466
- const routing = conf.routing;
19467
- const llm = routing?.llm;
19468
- if (llm && "batchMode" in llm && !("mode" in llm)) {
19469
- const batchMode = llm.batchMode;
19470
- if (typeof batchMode === "boolean") {
19471
- const mappedMode = batchMode ? "one-shot" : "per-story";
19472
- defaultConfigWarn(`routing.llm.batchMode is deprecated and will be removed in v1.0. Mapped to mode="${mappedMode}". Update your config to use routing.llm.mode instead.`);
19473
- return {
19474
- ...conf,
19475
- routing: {
19476
- ...routing,
19477
- llm: { ...llm, mode: mappedMode }
19478
- }
19479
- };
19480
- }
19481
- }
19482
- return conf;
19483
- }
19484
- function _applyLegacyReviewExecutionShim(conf, warn = defaultConfigWarn) {
19485
- let result = conf;
19486
- const execution = conf.execution;
19487
- if (execution && typeof execution === "object" && "inlineReview" in execution) {
19488
- warn("execution.inlineReview is a legacy field that has been removed. Remove it from your config.");
19489
- const { inlineReview: _ir, ...restExecution } = execution;
19490
- result = { ...result, execution: restExecution };
19491
- }
19492
- const review = result.review ?? conf.review;
19493
- if (review && typeof review === "object") {
19494
- let newReview = review;
19495
- const LEGACY_PLUGIN_MODE_VALUE = "per-story";
19496
- if ("pluginMode" in review && review.pluginMode === LEGACY_PLUGIN_MODE_VALUE) {
19497
- warn('review.pluginMode: "per-story" is a legacy value that has been removed. Remove it from your config (or set to "observational"/"gating").');
19498
- const { pluginMode: _pm, ...rest } = review;
19499
- newReview = rest;
19500
- }
19501
- const dialogue = newReview.dialogue;
19502
- if (dialogue && typeof dialogue === "object" && dialogue.enabled === true) {
19503
- warn("review.dialogue.enabled is a legacy field that has been removed. Remove it from your config.");
19504
- const { dialogue: _d, ...rest } = newReview;
19505
- newReview = rest;
19506
- }
19507
- result = { ...result, review: newReview };
19508
- }
19509
- return result;
19510
- }
19511
- function applyRoutingRetryDeprecationWarning(conf, warn = defaultConfigWarn) {
19512
- const routing = conf.routing;
19513
- const llm = routing?.llm;
19514
- if (!llm)
19515
- return conf;
19516
- if ("retries" in llm) {
19517
- warn("routing.llm.retries is deprecated (issue #856). " + "This value is still applied but will be removed in v1.0. " + "Retry policy is now declared on each operation \u2014 remove this key from your config.");
19518
- }
19519
- if ("retryDelayMs" in llm) {
19520
- warn("routing.llm.retryDelayMs is deprecated (issue #856). " + "This value is still applied but will be removed in v1.0. " + "Retry policy is now declared on each operation \u2014 remove this key from your config.");
19521
- }
19522
- return conf;
19523
- }
19524
- function applyConfigCompatShims(conf, logger) {
19525
- return _applyLegacyReviewExecutionShim(_applyRemovedRoutingKeysShim(applyRoutingRetryDeprecationWarning(applyBatchModeCompat(applyRemovedStrategyCompat(migrateLegacyReviewModelKey(migrateLegacyTestPattern(conf, logger), logger))))));
19526
- }
19527
19556
  async function loadConfig(startDir, cliOverrides) {
19528
19557
  let rawConfig = structuredClone(DEFAULT_CONFIG);
19558
+ const warnDedupe = createConfigWarnDedupe();
19529
19559
  const projDir = startDir ? basename2(startDir) === PROJECT_NAX_DIR ? startDir : findProjectDir(startDir) : findProjectDir();
19530
19560
  const projectRoot = startDir ? basename2(startDir) === PROJECT_NAX_DIR ? dirname(startDir) : startDir : process.cwd();
19531
19561
  const profileChain = await resolveProfileNames(cliOverrides ?? {}, process.env, projectRoot);
@@ -19537,14 +19567,14 @@ async function loadConfig(startDir, cliOverrides) {
19537
19567
  } catch {}
19538
19568
  if (globalConfRaw) {
19539
19569
  const { profile: _gProfile, ...globalConfStripped } = globalConfRaw;
19540
- const globalConf = applyConfigCompatShims(globalConfStripped, logger);
19570
+ const globalConf = applyConfigCompatShims(globalConfStripped, logger, warnDedupe);
19541
19571
  rawConfig = deepMergeConfig(rawConfig, globalConf);
19542
19572
  }
19543
19573
  if (projDir) {
19544
19574
  const projConf = await loadJsonFile(join3(projDir, "config.json"), "config");
19545
19575
  if (projConf) {
19546
19576
  const { profile: _pProfile, ...projConfStripped } = projConf;
19547
- const resolvedProjConf = applyConfigCompatShims(projConfStripped, logger);
19577
+ const resolvedProjConf = applyConfigCompatShims(projConfStripped, logger, warnDedupe);
19548
19578
  rawConfig = deepMergeConfig(rawConfig, resolvedProjConf);
19549
19579
  }
19550
19580
  }
@@ -19552,11 +19582,11 @@ async function loadConfig(startDir, cliOverrides) {
19552
19582
  const profileData = await loadProfile(name, projectRoot);
19553
19583
  const profileEnv = await loadProfileEnv(name, projectRoot);
19554
19584
  const resolvedProfileData = Object.keys(profileEnv).length > 0 ? resolveEnvVars(profileData, profileEnv) : profileData;
19555
- const shimmedProfileData = applyConfigCompatShims(resolvedProfileData, logger);
19585
+ const shimmedProfileData = applyConfigCompatShims(resolvedProfileData, logger, warnDedupe);
19556
19586
  rawConfig = deepMergeConfig(rawConfig, shimmedProfileData);
19557
19587
  }
19558
19588
  if (cliOverrides) {
19559
- const shimmedCliOverrides = applyConfigCompatShims(cliOverrides, logger);
19589
+ const shimmedCliOverrides = applyConfigCompatShims(cliOverrides, logger, warnDedupe);
19560
19590
  rawConfig = deepMergeConfig(rawConfig, shimmedCliOverrides);
19561
19591
  }
19562
19592
  rawConfig.profile = overlayChain.length > 0 ? overlayChain.join("+") : "default";
@@ -19662,6 +19692,7 @@ var init_loader = __esm(() => {
19662
19692
  init_errors();
19663
19693
  init_logger2();
19664
19694
  init_json_file();
19695
+ init_compat_shims();
19665
19696
  init_config_guards();
19666
19697
  init_path_security();
19667
19698
  init_paths();
@@ -21966,7 +21997,7 @@ function parseAcpxJsonLine(line, state) {
21966
21997
  state.text = "";
21967
21998
  state.sawJsonLine = true;
21968
21999
  }
21969
- const looksLikeJsonRpcShape = typeof event.method === "string" && event.params !== undefined || event.id !== undefined && (event.result && typeof event.result === "object" || event.error && typeof event.error === "object");
22000
+ const looksLikeJsonRpcShape = typeof event.method === "string" && event.params !== undefined || event.id !== undefined && event.result && typeof event.result === "object";
21970
22001
  if (event.jsonrpc !== "2.0" && looksLikeJsonRpcShape) {
21971
22002
  getSafeLogger()?.error("acp-adapter", "Unsupported or missing JSON-RPC protocol version in acpx output", {
21972
22003
  jsonrpc: event.jsonrpc,
@@ -22086,7 +22117,20 @@ function parseAcpxJsonLine(line, state) {
22086
22117
  if (event.stop_reason)
22087
22118
  state.stopReason = event.stop_reason;
22088
22119
  if (event.error) {
22089
- state.error = typeof event.error === "string" ? event.error : event.error.message ?? JSON.stringify(event.error);
22120
+ if (typeof event.error === "string") {
22121
+ state.error ??= event.error;
22122
+ } else {
22123
+ let errorMsg = typeof event.error.message === "string" ? event.error.message : JSON.stringify(event.error);
22124
+ const data = event.error.data;
22125
+ if (data && typeof data === "object") {
22126
+ const suffix = [data.acpxCode, data.detailCode].filter(Boolean).join("/");
22127
+ if (suffix)
22128
+ errorMsg = `${errorMsg} [${suffix}]`;
22129
+ if (!state.error && data.retryable === true)
22130
+ state.retryable = true;
22131
+ }
22132
+ state.error ??= errorMsg;
22133
+ }
22090
22134
  }
22091
22135
  } catch {
22092
22136
  if (!state.text && !state.sawJsonLine)
@@ -45525,7 +45569,7 @@ var package_default;
45525
45569
  var init_package = __esm(() => {
45526
45570
  package_default = {
45527
45571
  name: "@nathapp/nax",
45528
- version: "0.79.1",
45572
+ version: "0.79.2",
45529
45573
  description: "AI Coding Agent Orchestrator \u2014 loops until done",
45530
45574
  type: "module",
45531
45575
  bin: {
@@ -45638,8 +45682,8 @@ var init_version = __esm(() => {
45638
45682
  NAX_VERSION = package_default.version;
45639
45683
  NAX_COMMIT = (() => {
45640
45684
  try {
45641
- if (/^[0-9a-f]{6,10}$/.test("68f84ed9"))
45642
- return "68f84ed9";
45685
+ if (/^[0-9a-f]{6,10}$/.test("840b1200"))
45686
+ return "840b1200";
45643
45687
  } catch {}
45644
45688
  try {
45645
45689
  const result = Bun.spawnSync(["git", "rev-parse", "--short", "HEAD"], {
@@ -55547,17 +55591,33 @@ var init_cli = __esm(() => {
55547
55591
  CLIConfigSchema = exports_external.object({}).passthrough();
55548
55592
  });
55549
55593
 
55594
+ // src/interaction/plugins/telegram-config.ts
55595
+ function normalizeChatId(raw) {
55596
+ const chatId = raw.trim();
55597
+ return { chatId, unmatchable: !NUMERIC_CHAT_ID.test(chatId) };
55598
+ }
55599
+ var NUMERIC_CHAT_ID, TelegramConfigSchema;
55600
+ var init_telegram_config = __esm(() => {
55601
+ init_zod();
55602
+ NUMERIC_CHAT_ID = /^-?\d+$/;
55603
+ TelegramConfigSchema = exports_external.object({
55604
+ botToken: exports_external.string().optional(),
55605
+ chatId: exports_external.string().optional()
55606
+ });
55607
+ });
55608
+
55550
55609
  // src/interaction/plugins/telegram-format.ts
55551
55610
  function truncateUtf8Bytes(text, maxBytes) {
55552
55611
  if (maxBytes <= 0)
55553
55612
  return "";
55554
- if (Buffer.byteLength(text, "utf8") <= maxBytes)
55613
+ const encoded = Buffer.from(text, "utf8");
55614
+ if (encoded.length <= maxBytes)
55555
55615
  return text;
55556
- let end = text.length;
55557
- while (end > 0 && Buffer.byteLength(text.slice(0, end), "utf8") > maxBytes) {
55616
+ let end = maxBytes;
55617
+ while (end > 0 && (encoded[end] & 192) === 128) {
55558
55618
  end--;
55559
55619
  }
55560
- return text.slice(0, end);
55620
+ return encoded.toString("utf8", 0, end);
55561
55621
  }
55562
55622
  function truncateIdForCallbackData(id, suffix) {
55563
55623
  const suffixBytes = Buffer.byteLength(suffix, "utf8");
@@ -55568,6 +55628,9 @@ function truncateIdForCallbackData(id, suffix) {
55568
55628
  return truncateUtf8Bytes(`h${digest}`, maxIdBytes);
55569
55629
  }
55570
55630
  function buildCallbackData(id, suffix) {
55631
+ if (id.includes(":")) {
55632
+ throw new NaxError(`Interaction request id must not contain ":" \u2014 it is the callback_data field separator, and an id containing it can never be matched back to its prompt (id: ${id})`, "INTERACTION_INVALID_REQUEST_ID", { stage: "interaction", requestId: id });
55633
+ }
55571
55634
  return `${truncateIdForCallbackData(id, suffix)}${suffix}`;
55572
55635
  }
55573
55636
  function buildHeader(request) {
@@ -55691,26 +55754,21 @@ function getStageEmoji(stage) {
55691
55754
  }
55692
55755
  }
55693
55756
  var MAX_MESSAGE_CHARS = 4000, TELEGRAM_CALLBACK_DATA_MAX_BYTES = 64;
55757
+ var init_telegram_format = __esm(() => {
55758
+ init_errors();
55759
+ });
55694
55760
 
55695
55761
  // src/interaction/plugins/telegram.ts
55696
- function normalizeChatId(raw) {
55697
- const chatId = raw.trim();
55698
- return { chatId, unmatchable: !NUMERIC_CHAT_ID.test(chatId) };
55699
- }
55700
- var _telegramPluginDeps, CALLBACK_API_TIMEOUT_MS = 4000, NUMERIC_CHAT_ID, TelegramConfigSchema, TelegramInteractionPlugin;
55762
+ var _telegramPluginDeps, CALLBACK_API_TIMEOUT_MS = 4000, TelegramInteractionPlugin;
55701
55763
  var init_telegram = __esm(() => {
55702
- init_zod();
55703
55764
  init_logger2();
55765
+ init_telegram_config();
55766
+ init_telegram_format();
55704
55767
  _telegramPluginDeps = {
55705
55768
  fetch: globalThis.fetch.bind(globalThis),
55706
55769
  basePollBackoffMs: 1000,
55707
55770
  sleep: (ms) => new Promise((resolve14) => setTimeout(resolve14, ms))
55708
55771
  };
55709
- NUMERIC_CHAT_ID = /^-?\d+$/;
55710
- TelegramConfigSchema = exports_external.object({
55711
- botToken: exports_external.string().optional(),
55712
- chatId: exports_external.string().optional()
55713
- });
55714
55772
  TelegramInteractionPlugin = class TelegramInteractionPlugin {
55715
55773
  name = "telegram";
55716
55774
  logger = getSafeLogger();
@@ -55771,7 +55829,15 @@ var init_telegram = __esm(() => {
55771
55829
  await this.drainBacklog();
55772
55830
  }
55773
55831
  const header = buildHeader(request);
55774
- const keyboard = buildKeyboard(request);
55832
+ let keyboard = null;
55833
+ try {
55834
+ keyboard = buildKeyboard(request);
55835
+ } catch (err) {
55836
+ this.logger?.error("interaction", "Cannot build Telegram keyboard \u2014 sending prompt without buttons", {
55837
+ requestId: request.id,
55838
+ error: errorMessage(err)
55839
+ });
55840
+ }
55775
55841
  const body = buildBody(request);
55776
55842
  const chunks = splitText(body, MAX_MESSAGE_CHARS - header.length - 10);
55777
55843
  try {
@@ -55968,7 +56034,7 @@ ${partLabel}${chunks[i]}`;
55968
56034
  if (parts.length < 2)
55969
56035
  return null;
55970
56036
  const action = parts[1];
55971
- const value = parts.length > 2 ? parts[2] : undefined;
56037
+ const value = parts.length > 2 ? parts.slice(2).join(":") : undefined;
55972
56038
  const suffix = value !== undefined ? `:${action}:${value}` : `:${action}`;
55973
56039
  const expectedIdPart = truncateIdForCallbackData(requestId, suffix);
55974
56040
  if (parts[0] !== expectedIdPart)
@@ -56612,6 +56678,8 @@ var init_interaction = __esm(() => {
56612
56678
  init_chain();
56613
56679
  init_cli();
56614
56680
  init_telegram();
56681
+ init_telegram_config();
56682
+ init_telegram_format();
56615
56683
  init_webhook();
56616
56684
  init_triggers();
56617
56685
  init_init();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nathapp/nax",
3
- "version": "0.79.1",
3
+ "version": "0.79.2",
4
4
  "description": "AI Coding Agent Orchestrator — loops until done",
5
5
  "type": "module",
6
6
  "bin": {