nexus-agents 10.0.0 → 10.1.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.
@@ -7,7 +7,7 @@ import {
7
7
  VERSION,
8
8
  isNodeVersionSupported,
9
9
  recordServerTransport
10
- } from "./chunk-TQZSPGAZ.js";
10
+ } from "./chunk-AMK4BTAV.js";
11
11
  import {
12
12
  checkGatewayHealth,
13
13
  cliFailsVerdict,
@@ -33,6 +33,10 @@ import {
33
33
  createAllAdapters,
34
34
  createCliAdapter
35
35
  } from "./chunk-O3VPGING.js";
36
+ import {
37
+ DEFAULT_EXECUTION_MODE,
38
+ SecurityConfigSchema
39
+ } from "./chunk-TYLE7Z2S.js";
36
40
  import {
37
41
  getNexusTmpDir
38
42
  } from "./chunk-JPFRRNRR.js";
@@ -55,6 +59,7 @@ import {
55
59
  countRoutedOutcomesInFile,
56
60
  createLogger,
57
61
  err,
62
+ formatZodIssuesAsArray,
58
63
  gatewayCostStatus,
59
64
  getCliModelName,
60
65
  getDefaultModelForCli,
@@ -68,6 +73,7 @@ import {
68
73
  ok,
69
74
  resolveAuthEvidence,
70
75
  resolveGatewayCostDeclaration,
76
+ resolveInsideRoot,
71
77
  symbols,
72
78
  writeLine
73
79
  } from "./chunk-WI73OZAU.js";
@@ -87,21 +93,683 @@ import {
87
93
  parseBoolEnv
88
94
  } from "./chunk-LWOX65CP.js";
89
95
  import {
96
+ CliNameSchema,
97
+ TaskCategorySchema,
90
98
  resolveClassGuardMs
91
99
  } from "./chunk-4PN3UVZV.js";
92
100
 
93
101
  // src/cli/setup-data-dir.ts
94
- import { mkdirSync, existsSync as existsSync3 } from "node:fs";
102
+ import { mkdirSync, existsSync as existsSync4 } from "node:fs";
95
103
 
96
104
  // src/cli/doctor.ts
97
105
  import {
98
- existsSync as existsSync2,
99
- readFileSync as readFileSync2,
106
+ existsSync as existsSync3,
107
+ readFileSync as readFileSync3,
100
108
  readdirSync,
101
109
  accessSync,
102
110
  constants as fsConstants
103
111
  } from "node:fs";
104
112
 
113
+ // src/config/config-loader.ts
114
+ import { readFileSync, existsSync } from "node:fs";
115
+ import { resolve, join } from "node:path";
116
+ import * as yaml from "yaml";
117
+
118
+ // src/config/schemas.ts
119
+ import { z as z9 } from "zod";
120
+
121
+ // src/config/schemas-core.ts
122
+ import { z } from "zod";
123
+ var LoggingConfigSchema = z.object({
124
+ level: z.enum(["debug", "info", "warn", "error"]).default("info"),
125
+ format: z.enum(["json", "pretty"]).default("json"),
126
+ // Default to stderr: MCP stdio transport reserves stdout for JSON-RPC frames.
127
+ destination: z.enum(["stdout", "stderr", "file"]).default("stderr"),
128
+ filePath: z.string().optional()
129
+ });
130
+ var ProviderConfigSchema = z.object({
131
+ apiKey: z.string().optional(),
132
+ baseUrl: z.url().optional(),
133
+ timeout: z.number().positive().default(3e4),
134
+ maxRetries: z.number().nonnegative().default(3)
135
+ });
136
+ var ModelTiersSchema = z.object({
137
+ fast: z.array(z.string()).min(1),
138
+ balanced: z.array(z.string()).min(1),
139
+ powerful: z.array(z.string()).min(1)
140
+ });
141
+ var ModelConfigSchema = z.object({
142
+ default: z.string(),
143
+ tiers: ModelTiersSchema,
144
+ providers: z.record(z.string(), ProviderConfigSchema).optional()
145
+ });
146
+ var WorkflowConfigSchema = z.object({
147
+ templatesDir: z.string().default("./workflows"),
148
+ timeout: z.number().positive().default(3e5),
149
+ maxParallel: z.number().positive().default(5)
150
+ });
151
+
152
+ // src/config/schemas-expert.ts
153
+ import { z as z2 } from "zod";
154
+ var VALID_EXPERT_TIERS = ["fast", "balanced", "powerful"];
155
+ var VALID_EXPERT_DOMAINS = [
156
+ "code",
157
+ "security",
158
+ "architecture",
159
+ "documentation",
160
+ "testing",
161
+ "general"
162
+ ];
163
+ var MAX_SYSTEM_PROMPT_LENGTH = 4e3;
164
+ var CustomExpertDefinitionSchema = z2.object({
165
+ /** System prompt defining the expert's persona (max 4000 characters) */
166
+ systemPrompt: z2.string().min(1, "System prompt is required").max(
167
+ MAX_SYSTEM_PROMPT_LENGTH,
168
+ `System prompt must be at most ${String(MAX_SYSTEM_PROMPT_LENGTH)} characters`
169
+ ),
170
+ /** Model tier for routing */
171
+ tier: z2.enum(VALID_EXPERT_TIERS, {
172
+ error: `Invalid tier. Valid options: ${VALID_EXPERT_TIERS.join(", ")}`
173
+ }).default("balanced"),
174
+ /** Primary domain of expertise */
175
+ domain: z2.enum(VALID_EXPERT_DOMAINS, {
176
+ error: `Invalid domain. Valid options: ${VALID_EXPERT_DOMAINS.join(", ")}`
177
+ }).default("general"),
178
+ /** Secondary domains (optional) */
179
+ secondaryDomains: z2.array(z2.enum(VALID_EXPERT_DOMAINS)).optional(),
180
+ /** Expert capabilities */
181
+ capabilities: z2.array(z2.string().min(1)).min(1, "At least one capability is required").default(["task_execution"]),
182
+ /** Model temperature (0-1) */
183
+ temperature: z2.number().min(0).max(1).default(0.3),
184
+ /** Allowed tools (optional, unrestricted if not specified) */
185
+ tools: z2.array(z2.string()).optional(),
186
+ /** Human-readable description */
187
+ description: z2.string().optional(),
188
+ /** Weight for expert selection scoring (0-1) */
189
+ weight: z2.number().min(0).max(1).default(1),
190
+ /** Whether this expert is currently available */
191
+ available: z2.boolean().default(true)
192
+ });
193
+ var ExpertDefinitionSchema = z2.object({
194
+ prompt: z2.string().min(1),
195
+ tier: z2.enum(["fast", "balanced", "powerful"]).default("balanced"),
196
+ temperature: z2.number().min(0).max(1).default(0.3),
197
+ tools: z2.array(z2.string()).optional()
198
+ });
199
+ var ExpertConfigSchema = z2.object({
200
+ /** Enable built-in experts */
201
+ builtin: z2.boolean().default(true),
202
+ /** Custom expert definitions keyed by expert ID */
203
+ custom: z2.record(
204
+ z2.string().regex(/^[a-z][a-z0-9_]*$/, {
205
+ message: "Expert ID must start with a letter and contain only lowercase letters, numbers, and underscores"
206
+ }),
207
+ CustomExpertDefinitionSchema
208
+ ).optional()
209
+ });
210
+
211
+ // src/config/schemas-observability.ts
212
+ import { z as z3 } from "zod";
213
+ var EventBusConfigSchema = z3.object({
214
+ /** Enable EventBus integration (default: true) */
215
+ enabled: z3.boolean().default(true),
216
+ /** Maximum events to retain in history (default: 1000) */
217
+ maxHistorySize: z3.number().positive().default(1e3),
218
+ /** Event patterns to subscribe to (default: all major patterns) */
219
+ subscriptions: z3.object({
220
+ /** Subscribe to consensus events (consensus.*) */
221
+ consensus: z3.boolean().default(true),
222
+ /** Subscribe to agent events (agent.*) */
223
+ agent: z3.boolean().default(true),
224
+ /** Subscribe to protocol events (protocol.*) */
225
+ protocol: z3.boolean().default(true),
226
+ /** Subscribe to session events (session.*) */
227
+ session: z3.boolean().default(true),
228
+ /** Subscribe to message events (message.*) */
229
+ message: z3.boolean().default(false),
230
+ // Off by default (high volume)
231
+ /** Subscribe to byzantine detection events (byzantine.*) */
232
+ byzantine: z3.boolean().default(true)
233
+ }).default(() => ({
234
+ consensus: true,
235
+ agent: true,
236
+ protocol: true,
237
+ session: true,
238
+ message: false,
239
+ byzantine: true
240
+ })),
241
+ /** Logging configuration for events */
242
+ logging: z3.object({
243
+ /** Log level for frequent events (default: debug) */
244
+ frequentEventLevel: z3.enum(["debug", "info"]).default("debug"),
245
+ /** Log level for important events (default: info) */
246
+ importantEventLevel: z3.enum(["debug", "info"]).default("info")
247
+ }).default(() => ({
248
+ frequentEventLevel: "debug",
249
+ importantEventLevel: "info"
250
+ }))
251
+ });
252
+ var ObservabilityConfigSchema = z3.object({
253
+ /** EventBus configuration */
254
+ eventBus: EventBusConfigSchema.optional(),
255
+ /** SwarmObserver maximum events (default: 10000) */
256
+ swarmObserverMaxEvents: z3.number().positive().default(1e4)
257
+ });
258
+
259
+ // src/config/schemas-skills.ts
260
+ import { z as z4 } from "zod";
261
+ var ExternalPackSourceSchema = z4.object({
262
+ /** Pack name for identification */
263
+ name: z4.string().min(1),
264
+ /** Source: npm package name or local file path */
265
+ source: z4.string().min(1),
266
+ /** Whether the pack is enabled (default: true) */
267
+ enabled: z4.boolean().default(true)
268
+ });
269
+ var SkillLibraryConfigSchema = z4.object({
270
+ /** Whether skill library is enabled (default: true) */
271
+ enabled: z4.boolean().default(true),
272
+ /** Maximum skills to store (default: 1000) */
273
+ maxSkills: z4.number().int().positive().default(1e3),
274
+ /** Minimum success rate to keep skill (0-1, default: 0.3) */
275
+ minSuccessRateForRetention: z4.number().min(0).max(1).default(0.3),
276
+ /** Number of executions before evaluating retention (default: 5) */
277
+ executionsBeforeEvaluation: z4.number().int().nonnegative().default(5),
278
+ /** Enable automatic skill pruning (default: true) */
279
+ enablePruning: z4.boolean().default(true),
280
+ /** Whether to track detailed execution history (default: true) */
281
+ trackExecutionHistory: z4.boolean().default(true),
282
+ /** Maximum execution history entries per skill (default: 100) */
283
+ maxHistoryPerSkill: z4.number().int().positive().default(100),
284
+ /** External skill pack sources (Issue #654) */
285
+ externalPacks: z4.array(ExternalPackSourceSchema).optional()
286
+ });
287
+
288
+ // src/config/schemas-sica.ts
289
+ import { z as z5 } from "zod";
290
+ var SicaConfigSchema = z5.object({
291
+ /** Whether SICA wrapping is enabled (default: false) */
292
+ enabled: z5.boolean().default(false),
293
+ /** Minimum executions before considering improvement (default: 10) */
294
+ minExecutionsForImprovement: z5.number().int().positive().default(10),
295
+ /** Success rate threshold to trigger improvement (0-1, default: 0.7) */
296
+ improvementThreshold: z5.number().min(0).max(1).default(0.7),
297
+ /** Maximum concurrent versions to evaluate (default: 3) */
298
+ maxActiveVersions: z5.number().int().positive().max(10).default(3),
299
+ /** Whether to auto-select best version (default: true) */
300
+ autoSelectBest: z5.boolean().default(true),
301
+ /** Improvement cooldown in milliseconds (default: 60000) */
302
+ improvementCooldownMs: z5.number().int().nonnegative().default(6e4),
303
+ /** Enable observability logging for SICA events (default: true) */
304
+ enableObservability: z5.boolean().default(true)
305
+ });
306
+
307
+ // src/config/schemas-gateway.ts
308
+ import { z as z6 } from "zod";
309
+ var TierNameSchema = z6.enum(["DIRECT", "ANALYZED", "ORCHESTRATED"]);
310
+ var ALLOWED_COMMANDS = ["node", "npx", "python", "python3", "uvx", "docker"];
311
+ var UpstreamServerSchema = z6.object({
312
+ /** Unique name for this upstream server (used as tool prefix). */
313
+ name: z6.string().min(1).max(50).regex(/^[a-z0-9_-]+$/),
314
+ /** Command to spawn the server. Must be in allowlist. */
315
+ command: z6.enum(ALLOWED_COMMANDS),
316
+ /** Arguments to pass to the command. */
317
+ args: z6.array(z6.string().max(500)).default([]),
318
+ /** Environment variables (use {env:VAR} references, not plaintext secrets). */
319
+ env: z6.record(z6.string(), z6.string()).optional(),
320
+ /** Whether to connect lazily on first tool call (default: true). */
321
+ lazy: z6.boolean().default(true),
322
+ /** Connection timeout in ms (default: 10000). */
323
+ timeoutMs: z6.number().int().min(1e3).max(6e4).default(1e4)
324
+ });
325
+ var MAX_UPSTREAM_SERVERS = 5;
326
+ var GatewayConfigSchema = z6.object({
327
+ /** Enable gateway tier dispatch logging (default: true). */
328
+ enabled: z6.boolean().default(true),
329
+ /**
330
+ * Per-tool tier overrides.
331
+ * Keys are tool names (e.g., 'delegate_to_model'), values are tier names.
332
+ * Overrides the default tier from TOOL_TIER_MAP in tier-classifier.ts.
333
+ */
334
+ tierOverrides: z6.record(z6.string(), TierNameSchema).optional(),
335
+ /**
336
+ * Upstream MCP servers to compose with (#1498).
337
+ * Tools from upstream servers are available as prefixed tools (e.g., tavily.search).
338
+ */
339
+ upstreamServers: z6.array(UpstreamServerSchema).max(MAX_UPSTREAM_SERVERS).optional()
340
+ });
341
+
342
+ // src/config/schemas-routing.ts
343
+ import { z as z7 } from "zod";
344
+ var BudgetConstraintsSchema = z7.object({
345
+ /** Maximum tokens per request */
346
+ maxTokens: z7.number().positive().optional(),
347
+ /** Maximum cost per request in USD */
348
+ maxCostUsd: z7.number().positive().optional(),
349
+ /** Maximum latency per request in milliseconds */
350
+ maxLatencyMs: z7.number().positive().optional(),
351
+ /**
352
+ * Per-task-class cost ceilings in USD, keyed by `TaskCategory`
353
+ * (#4196, #4214). Keys are validated against the enum so a typo'd
354
+ * class fails config parsing instead of silently configuring nothing.
355
+ * Absent → no ceiling (OFF/unlimited). Enforced only under
356
+ * `NEXUS_BILLING_MODE=api`; a candidate with missing registry pricing
357
+ * fails a configured ceiling (fail-closed).
358
+ */
359
+ taskClassMaxCostUsd: z7.partialRecord(TaskCategorySchema, z7.number().positive()).optional()
360
+ }).optional();
361
+ var TopsisCriterionSchema = z7.object({
362
+ /** Criterion name (e.g., 'quality', 'cost', 'latency') */
363
+ name: z7.string().min(1),
364
+ /** Weight for this criterion (0-1, should sum to 1 across all criteria) */
365
+ weight: z7.number().min(0).max(1),
366
+ /** Whether higher values are better (true) or lower is better (false) */
367
+ beneficial: z7.boolean()
368
+ });
369
+ var TopsisConfigSchema = z7.object({
370
+ /** Criteria with weights (must sum to 1.0) */
371
+ criteria: z7.array(TopsisCriterionSchema).optional(),
372
+ /** Minimum acceptable quality score (0-10) */
373
+ minQualityThreshold: z7.number().min(0).max(10).default(5),
374
+ /** Maximum acceptable latency in milliseconds (optional) */
375
+ maxLatencyMs: z7.number().positive().optional(),
376
+ /** Maximum acceptable cost per request in USD (optional) */
377
+ maxCostPerRequest: z7.number().positive().optional(),
378
+ /** Whether to log detailed scoring info */
379
+ verbose: z7.boolean().default(false)
380
+ }).optional();
381
+ var DifficultyWeightsConfigSchema = z7.object({
382
+ /** Weight for reasoning difficulty (0-1) */
383
+ reasoning: z7.number().min(0).max(1).default(0.3),
384
+ /** Weight for knowledge difficulty (0-1) */
385
+ knowledge: z7.number().min(0).max(1).default(0.15),
386
+ /** Weight for creativity difficulty (0-1) */
387
+ creativity: z7.number().min(0).max(1).default(0.15),
388
+ /** Weight for precision difficulty (0-1) */
389
+ precision: z7.number().min(0).max(1).default(0.25),
390
+ /** Weight for context length difficulty (0-1) */
391
+ context_length: z7.number().min(0).max(1).default(0.15)
392
+ });
393
+ var DifficultyThresholdsSchema = z7.object({
394
+ /** Upper bound for 'easy' classification (0-1) */
395
+ easyUpperBound: z7.number().min(0).max(1).default(0.3),
396
+ /** Lower bound for 'hard' classification (0-1) */
397
+ hardLowerBound: z7.number().min(0).max(1).default(0.7)
398
+ });
399
+ var DifficultyLevelSchema = z7.enum(["easy", "medium", "hard"]);
400
+ var ModelTierSchema = z7.enum(["fast", "balanced", "powerful"]);
401
+ var ZeroRouterConfigSchema = z7.object({
402
+ /** Difficulty thresholds for level classification */
403
+ thresholds: DifficultyThresholdsSchema.optional(),
404
+ /** Weights for difficulty aggregation */
405
+ weights: DifficultyWeightsConfigSchema.optional(),
406
+ /** Mapping from difficulty level to model tier */
407
+ difficultyToTier: z7.record(DifficultyLevelSchema, ModelTierSchema).optional(),
408
+ /** Mapping from model tier to CLI preference order */
409
+ tierToClis: z7.record(ModelTierSchema, z7.array(CliNameSchema)).optional(),
410
+ /** Enable adaptive calibration from outcomes */
411
+ enableCalibration: z7.boolean().default(true),
412
+ /** Maximum outcomes to store for calibration */
413
+ maxCalibrationOutcomes: z7.number().int().positive().default(1e3),
414
+ /** Minimum outcomes before applying calibration adjustments */
415
+ minCalibrationOutcomes: z7.number().int().positive().default(50),
416
+ /** Verbose logging */
417
+ verbose: z7.boolean().default(false)
418
+ }).optional();
419
+ var LatencyTrackerConfigSchema = z7.object({
420
+ /** Maximum number of samples to keep per CLI */
421
+ windowSize: z7.number().int().positive().default(100),
422
+ /** Time-weighted decay factor (0-1, higher = more weight to recent) */
423
+ decayFactor: z7.number().min(0).max(1).default(0.95),
424
+ /** Maximum age of samples in milliseconds before forced eviction */
425
+ maxSampleAgeMs: z7.number().int().positive().default(36e5),
426
+ /** Percentiles to calculate */
427
+ percentiles: z7.array(z7.number().min(0).max(100)).max(20).default([50, 95, 99])
428
+ }).optional();
429
+ var RoutingMemoryConfigSchema = z7.object({
430
+ /** Minimum observations before using learned routing */
431
+ minObservations: z7.number().int().positive().default(5),
432
+ /** Confidence threshold for using cached decisions */
433
+ confidenceThreshold: z7.number().min(0).max(1).default(0.6),
434
+ /** Success rate threshold for trusting a routing pattern */
435
+ successRateThreshold: z7.number().min(0).max(1).default(0.7),
436
+ /** Maximum age of cached actions in milliseconds */
437
+ actionCacheMaxAgeMs: z7.number().int().positive().default(36e5)
438
+ }).optional();
439
+ var RoutingConfigSchema = z7.object({
440
+ /**
441
+ * Pipeline stage toggles.
442
+ * Enable or disable individual routing stages.
443
+ */
444
+ stages: z7.object({
445
+ /** Enable budget filtering stage */
446
+ budgetFilter: z7.boolean().default(true),
447
+ /** Enable ZeroRouter difficulty-based routing */
448
+ zeroRouter: z7.boolean().default(true),
449
+ /** Enable preference-trained routing */
450
+ preferenceRouting: z7.boolean().default(false),
451
+ /** Enable TOPSIS ranking */
452
+ topsisRanking: z7.boolean().default(true),
453
+ /** Enable LinUCB selection */
454
+ linucbSelection: z7.boolean().default(true),
455
+ /** Enable latency tracking */
456
+ latencyTracking: z7.boolean().default(true),
457
+ /** Enable routing memory for learned routing */
458
+ routingMemory: z7.boolean().default(false),
459
+ /** Enable confidence cascade stage (Issue #755, ADR-0005) */
460
+ confidenceCascade: z7.boolean().default(false),
461
+ /** Enable capability match stage (Issue #755, ADR-0005) */
462
+ capabilityMatch: z7.boolean().default(false),
463
+ /** Enable quality constraint stage (Issue #755, ADR-0005) */
464
+ qualityConstraint: z7.boolean().default(false),
465
+ /** Enable resource strategy stage for budget-aware oscillation (Issue #998) */
466
+ resourceStrategy: z7.boolean().default(true),
467
+ /** Enable strategy distillation for learned routing rules (Issue #999) */
468
+ strategyDistillation: z7.boolean().default(false)
469
+ }).optional(),
470
+ /**
471
+ * Budget constraints for routing decisions.
472
+ */
473
+ budget: BudgetConstraintsSchema,
474
+ /**
475
+ * TOPSIS multi-criteria decision making configuration.
476
+ */
477
+ topsis: TopsisConfigSchema,
478
+ /**
479
+ * ZeroRouter difficulty-based routing configuration.
480
+ */
481
+ zeroRouter: ZeroRouterConfigSchema,
482
+ /**
483
+ * Latency tracker configuration.
484
+ */
485
+ latencyTracker: LatencyTrackerConfigSchema,
486
+ /**
487
+ * Routing memory configuration.
488
+ */
489
+ routingMemory: RoutingMemoryConfigSchema,
490
+ /**
491
+ * LinUCB bandit parameters.
492
+ */
493
+ linucb: z7.object({
494
+ /** Exploration parameter (higher = more exploration) */
495
+ alpha: z7.number().positive().default(1)
496
+ }).optional(),
497
+ /**
498
+ * Preference router parameters.
499
+ */
500
+ preference: z7.object({
501
+ /** Minimum data points before using learned preferences */
502
+ minDataPoints: z7.number().int().positive().default(10)
503
+ }).optional(),
504
+ /**
505
+ * Weight for latency score in final routing (0-1).
506
+ */
507
+ latencyScoreWeight: z7.number().min(0).max(1).default(0.2)
508
+ });
509
+
510
+ // src/config/schemas-memory.ts
511
+ import { z as z8 } from "zod";
512
+ var unitInterval = z8.number().min(0).max(1);
513
+ var MIN_DECAY_INTERVAL_MS = 1e3;
514
+ var MemoryDecayConfigSchema = z8.object({
515
+ /** Whether decay runs at all (default: true) */
516
+ enabled: z8.boolean().optional(),
517
+ /**
518
+ * Interval between automatic decay runs in ms (default: 1 hour).
519
+ * Floor: {@link MIN_DECAY_INTERVAL_MS}. `runDecay` has no re-entrancy
520
+ * guard, so a sub-second cadence invites overlapping sweeps over the same
521
+ * stores; 1000 ms is the smallest value at which that is implausible.
522
+ */
523
+ decayIntervalMs: z8.number().int().min(MIN_DECAY_INTERVAL_MS).optional(),
524
+ /** Age in days before superseded beliefs are pruned (default: 30) */
525
+ beliefMaxAgeDays: z8.number().int().positive().optional(),
526
+ /** Agentic entries before importance-based eviction starts (default: 10000) */
527
+ agenticMaxEntries: z8.number().int().positive().optional(),
528
+ /** Importance below which agentic entries are evicted, 0-1 (default: 0.3) */
529
+ agenticImportanceThreshold: unitInterval.optional(),
530
+ /** Priority score below which adaptive entries are evicted, 0-1 (default: 0.2) */
531
+ adaptivePriorityThreshold: unitInterval.optional(),
532
+ /** Run MobiMem TTL eviction on each coordinated decay (default: true) */
533
+ mobimemEvictOnDecay: z8.boolean().optional(),
534
+ /** Check cross-references before evicting (default: true) */
535
+ checkCrossReferences: z8.boolean().optional(),
536
+ /** Grace period in ms before removing cross-referenced items (default: 7 days) */
537
+ crossReferenceGracePeriodMs: z8.number().int().nonnegative().optional()
538
+ });
539
+ var MemoryConfigSchema = z8.object({
540
+ /** Coordinated decay knobs (#5097) */
541
+ decay: MemoryDecayConfigSchema.optional()
542
+ });
543
+
544
+ // src/config/schemas.ts
545
+ var AppConfigSchema = z9.object({
546
+ models: ModelConfigSchema,
547
+ experts: ExpertConfigSchema.optional(),
548
+ workflows: WorkflowConfigSchema.optional(),
549
+ security: SecurityConfigSchema.optional(),
550
+ logging: LoggingConfigSchema.optional(),
551
+ /** Observability configuration (Issue #307) */
552
+ observability: ObservabilityConfigSchema.optional(),
553
+ /** Routing configuration (Issue #475) - used in orchestrator mode */
554
+ routing: RoutingConfigSchema.optional(),
555
+ /** Skill library configuration (Issue #491) */
556
+ skills: SkillLibraryConfigSchema.optional(),
557
+ /** SICA self-improvement configuration (Issue #492) */
558
+ sica: SicaConfigSchema.optional(),
559
+ /** Gateway middleware configuration (Issue #897) */
560
+ gateway: GatewayConfigSchema.optional(),
561
+ /** Memory configuration — today only `decay` reaches runtime (Issue #5097) */
562
+ memory: MemoryConfigSchema.optional()
563
+ });
564
+ var defaultConfig = {
565
+ models: {
566
+ default: "claude-sonnet",
567
+ tiers: {
568
+ fast: ["claude-haiku", "gemini-flash"],
569
+ balanced: ["claude-sonnet", "gemini-pro"],
570
+ powerful: ["claude-opus", "codex-5.3"]
571
+ }
572
+ },
573
+ logging: {
574
+ level: "info",
575
+ format: "json",
576
+ // stderr is the safe default: MCP stdio transport reserves stdout for
577
+ // JSON-RPC frames. Any log written to stdout corrupts the transport.
578
+ destination: "stderr"
579
+ },
580
+ security: {
581
+ allowedPaths: ["./"],
582
+ blockedPatterns: [],
583
+ rateLimit: {
584
+ enabled: true,
585
+ requestsPerMinute: 60
586
+ },
587
+ policy: {
588
+ defaultMode: DEFAULT_EXECUTION_MODE,
589
+ policyMode: "enforce"
590
+ },
591
+ timeout: {
592
+ defaultTimeoutMs: 3e4,
593
+ maxTimeoutMs: 3e5,
594
+ enableLogging: true,
595
+ uriValidation: true
596
+ }
597
+ }
598
+ };
599
+
600
+ // src/config/config-loader.ts
601
+ var DEFAULT_CONFIG_FILE = "nexus-agents.yaml";
602
+ var ALTERNATE_CONFIG_FILE = "nexus-agents.yml";
603
+ var CONFIG_LOOKUP_PATHS = [
604
+ `.nexus-agents/${DEFAULT_CONFIG_FILE}`,
605
+ `.nexus-agents/${ALTERNATE_CONFIG_FILE}`,
606
+ DEFAULT_CONFIG_FILE,
607
+ ALTERNATE_CONFIG_FILE
608
+ ];
609
+ var ConfigLoadError = class extends Error {
610
+ code;
611
+ constructor(message, code, cause) {
612
+ super(message, { cause });
613
+ this.name = "ConfigLoadError";
614
+ this.code = code;
615
+ }
616
+ };
617
+ function validatePath(userPath, root) {
618
+ const resolved = resolveInsideRoot(userPath, root);
619
+ if (resolved === null) {
620
+ return err(
621
+ new ConfigLoadError(
622
+ `Config path traversal detected: ${userPath} escapes ${root}`,
623
+ "PATH_TRAVERSAL"
624
+ )
625
+ );
626
+ }
627
+ return ok(resolved);
628
+ }
629
+ function findConfigPath(cwd) {
630
+ const envPath = process.env["NEXUS_CONFIG_PATH"];
631
+ if (envPath !== void 0 && envPath !== "") {
632
+ const validation = validatePath(envPath, cwd);
633
+ if (validation.ok && existsSync(validation.value)) {
634
+ return validation.value;
635
+ }
636
+ }
637
+ for (const rel of CONFIG_LOOKUP_PATHS) {
638
+ const abs = resolve(cwd, rel);
639
+ if (existsSync(abs)) {
640
+ return abs;
641
+ }
642
+ }
643
+ const globalDir = getNexusDataDir();
644
+ const globalYamlPath = join(globalDir, DEFAULT_CONFIG_FILE);
645
+ if (existsSync(globalYamlPath)) {
646
+ return globalYamlPath;
647
+ }
648
+ const globalYmlPath = join(globalDir, ALTERNATE_CONFIG_FILE);
649
+ if (existsSync(globalYmlPath)) {
650
+ return globalYmlPath;
651
+ }
652
+ return void 0;
653
+ }
654
+ function parseYaml(content) {
655
+ try {
656
+ const parsed = yaml.parse(content);
657
+ return ok(parsed);
658
+ } catch (error) {
659
+ const message = error instanceof Error ? error.message : "Unknown YAML parse error";
660
+ return err(
661
+ new ConfigLoadError(`YAML parse error: ${message}`, "YAML_PARSE_ERROR", error)
662
+ );
663
+ }
664
+ }
665
+ var PROTOTYPE_POLLUTION_KEYS = /* @__PURE__ */ new Set([
666
+ "__proto__",
667
+ "constructor",
668
+ "prototype"
669
+ ]);
670
+ function isMergeableObject(value) {
671
+ return typeof value === "object" && value !== null && !Array.isArray(value);
672
+ }
673
+ function deepMerge(target, source) {
674
+ const result = { ...target };
675
+ for (const key of Object.keys(source)) {
676
+ if (PROTOTYPE_POLLUTION_KEYS.has(key)) continue;
677
+ const sourceValue = source[key];
678
+ if (sourceValue === void 0) continue;
679
+ const targetValue = target[key];
680
+ if (isMergeableObject(sourceValue) && isMergeableObject(targetValue)) {
681
+ result[key] = deepMerge(targetValue, sourceValue);
682
+ } else {
683
+ result[key] = sourceValue;
684
+ }
685
+ }
686
+ return result;
687
+ }
688
+ function loadDefaultConfig(logger2) {
689
+ logger2.debug("No config file found, using defaults");
690
+ const validated = AppConfigSchema.safeParse(defaultConfig);
691
+ if (!validated.success) {
692
+ return err(
693
+ new ConfigLoadError(
694
+ `Default config validation failed: ${validated.error.message}`,
695
+ "VALIDATION_ERROR"
696
+ )
697
+ );
698
+ }
699
+ return ok({
700
+ config: validated.data,
701
+ usingDefaults: true,
702
+ warnings: ["No config file found, using default configuration"]
703
+ });
704
+ }
705
+ function readAndParseConfig(configPath, mergeDefaults, logger2) {
706
+ let content;
707
+ try {
708
+ content = readFileSync(configPath, "utf-8");
709
+ } catch (error) {
710
+ const message = error instanceof Error ? error.message : "Unknown error";
711
+ return err(
712
+ new ConfigLoadError(
713
+ `Failed to read config file: ${message}`,
714
+ "FILE_READ_ERROR",
715
+ error
716
+ )
717
+ );
718
+ }
719
+ const parseResult = parseYaml(content);
720
+ if (!parseResult.ok) return err(parseResult.error);
721
+ if (mergeDefaults && typeof parseResult.value === "object" && parseResult.value !== null) {
722
+ logger2.debug("Merged config with defaults");
723
+ return ok(
724
+ deepMerge(
725
+ defaultConfig,
726
+ parseResult.value
727
+ )
728
+ );
729
+ }
730
+ return ok(parseResult.value);
731
+ }
732
+ function validateConfig(configData, configPath, logger2) {
733
+ const validation = AppConfigSchema.safeParse(configData);
734
+ if (!validation.success) {
735
+ const issues = formatZodIssuesAsArray(validation.error);
736
+ return err(
737
+ new ConfigLoadError(`Config validation failed:
738
+ ${issues.join("\n")}`, "VALIDATION_ERROR")
739
+ );
740
+ }
741
+ logger2.info("Configuration loaded successfully", { configPath });
742
+ return ok({ config: validation.data, configPath, usingDefaults: false, warnings: [] });
743
+ }
744
+ function loadConfig(options = {}) {
745
+ const { configPath: explicitPath, cwd = process.cwd(), mergeDefaults = true } = options;
746
+ const logger2 = options.logger ?? createLogger({ component: "ConfigLoader" });
747
+ let configPath;
748
+ if (explicitPath !== void 0) {
749
+ const validation = validatePath(explicitPath, cwd);
750
+ if (!validation.ok) return err(validation.error);
751
+ configPath = validation.value;
752
+ } else {
753
+ configPath = findConfigPath(cwd);
754
+ }
755
+ if (configPath === void 0) return loadDefaultConfig(logger2);
756
+ const parseResult = readAndParseConfig(configPath, mergeDefaults, logger2);
757
+ if (!parseResult.ok) return err(parseResult.error);
758
+ return validateConfig(parseResult.value, configPath, logger2);
759
+ }
760
+ var loadedConfig;
761
+ function getConfig(options) {
762
+ if (loadedConfig !== void 0) {
763
+ return loadedConfig;
764
+ }
765
+ const result = loadConfig(options);
766
+ if (!result.ok) {
767
+ throw result.error;
768
+ }
769
+ loadedConfig = result.value;
770
+ return loadedConfig;
771
+ }
772
+
105
773
  // src/cli/doctor-scratch-space.ts
106
774
  import { statSync, statfsSync } from "node:fs";
107
775
  import { tmpdir } from "node:os";
@@ -593,6 +1261,7 @@ function failingVerdictTerms(result, live) {
593
1261
  const terms = gatewayTerms(result.gateway, gateway);
594
1262
  if (!result.nodeVersion.supported) terms.push("node version");
595
1263
  if (!result.mcpServerReady) terms.push("MCP server");
1264
+ if (result.configFile.error !== void 0) terms.push("configuration");
596
1265
  if (installFreshnessFailsVerdict(result.installFreshness)) {
597
1266
  terms.push("install freshness");
598
1267
  }
@@ -766,7 +1435,9 @@ function printApiKeysCheck(keys) {
766
1435
  }
767
1436
  }
768
1437
  function printConfigFileCheck(check) {
769
- if (check.found && check.path !== null) {
1438
+ if (check.error !== void 0) {
1439
+ writeLine(`${formatStatus(false)} Configuration failed: ${check.error}`);
1440
+ } else if (check.found && check.path !== null) {
770
1441
  writeLine(`${formatStatus(true)} Configuration loaded: ${check.path}`);
771
1442
  } else {
772
1443
  writeLine(`${formatStatus(false, true)} Configuration file: Not found`);
@@ -1012,8 +1683,8 @@ function printHarnessAlignment(check) {
1012
1683
  }
1013
1684
 
1014
1685
  // src/cli/doctor-harness-alignment.ts
1015
- import { existsSync, readFileSync } from "node:fs";
1016
- import { dirname, join, resolve } from "node:path";
1686
+ import { existsSync as existsSync2, readFileSync as readFileSync2 } from "node:fs";
1687
+ import { dirname, join as join2, resolve as resolve2 } from "node:path";
1017
1688
  var HARNESS_FILES = [
1018
1689
  { harness: "Cursor", path: ".cursor/rules/agents.mdc" },
1019
1690
  { harness: "Windsurf", path: ".windsurf/rules/agents.md" },
@@ -1028,9 +1699,9 @@ function checkHarnessAlignment(cwd = process.cwd()) {
1028
1699
  const projectRoot = findProjectRoot(cwd);
1029
1700
  const inProject = projectRoot !== null;
1030
1701
  const base = projectRoot ?? cwd;
1031
- const agentsMdExists = existsSync(join(base, "AGENTS.md"));
1702
+ const agentsMdExists = existsSync2(join2(base, "AGENTS.md"));
1032
1703
  const files = HARNESS_FILES.map(
1033
- ({ harness, path }) => inspectFile(harness, path, join(base, path))
1704
+ ({ harness, path }) => inspectFile(harness, path, join2(base, path))
1034
1705
  );
1035
1706
  const alignedCount = files.filter((f) => f.exists && f.redirectsToAgentsMd).length;
1036
1707
  const driftCount = files.filter((f) => f.exists && !f.redirectsToAgentsMd).length;
@@ -1048,16 +1719,16 @@ function findProjectRoot(start) {
1048
1719
  return findRepoRoot(start) ?? findPackageJsonAncestor(start);
1049
1720
  }
1050
1721
  function findPackageJsonAncestor(start) {
1051
- let current = resolve(start);
1722
+ let current = resolve2(start);
1052
1723
  for (; ; ) {
1053
- if (existsSync(join(current, "package.json"))) return current;
1724
+ if (existsSync2(join2(current, "package.json"))) return current;
1054
1725
  const parent = dirname(current);
1055
1726
  if (parent === current) return null;
1056
1727
  current = parent;
1057
1728
  }
1058
1729
  }
1059
1730
  function inspectFile(harness, relativePath, absolutePath) {
1060
- if (!existsSync(absolutePath)) {
1731
+ if (!existsSync2(absolutePath)) {
1061
1732
  return {
1062
1733
  harness,
1063
1734
  path: relativePath,
@@ -1067,7 +1738,7 @@ function inspectFile(harness, relativePath, absolutePath) {
1067
1738
  };
1068
1739
  }
1069
1740
  try {
1070
- const content = readFileSync(absolutePath, "utf-8");
1741
+ const content = readFileSync2(absolutePath, "utf-8");
1071
1742
  return {
1072
1743
  harness,
1073
1744
  path: relativePath,
@@ -1089,12 +1760,6 @@ function inspectFile(harness, relativePath, absolutePath) {
1089
1760
  // src/cli/doctor.ts
1090
1761
  import { execFileSync } from "node:child_process";
1091
1762
  var API_KEY_VARS = ["ANTHROPIC_API_KEY", "OPENAI_API_KEY", "GOOGLE_AI_API_KEY"];
1092
- var CONFIG_FILE_PATHS = [
1093
- "./.nexus-agents/nexus-agents.yaml",
1094
- "./.nexus-agents/nexus-agents.yml",
1095
- "./nexus-agents.yaml",
1096
- "./nexus-agents.yml"
1097
- ];
1098
1763
  var DATA_SUBDIRECTORIES = [
1099
1764
  "memory",
1100
1765
  "memory/beliefs",
@@ -1228,12 +1893,11 @@ function checkApiKeys() {
1228
1893
  }));
1229
1894
  }
1230
1895
  function checkConfigFile() {
1231
- for (const configPath of CONFIG_FILE_PATHS) {
1232
- if (existsSync2(configPath)) {
1233
- return { found: true, path: configPath };
1234
- }
1896
+ const result = loadConfig();
1897
+ if (!result.ok) {
1898
+ return { found: true, path: null, error: result.error.message };
1235
1899
  }
1236
- return { found: false, path: null };
1900
+ return { found: !result.value.usingDefaults, path: result.value.configPath ?? null };
1237
1901
  }
1238
1902
  function checkMcpServerReady() {
1239
1903
  try {
@@ -1271,11 +1935,11 @@ function buildRegistryAdvisory(cliResults, disabledClis, gateway) {
1271
1935
  function hasPriorUsage() {
1272
1936
  try {
1273
1937
  const root = getNexusDataDir();
1274
- if (!existsSync2(root)) return false;
1938
+ if (!existsSync3(root)) return false;
1275
1939
  for (const sub of ["audit", "learning", "sessions", "voting"]) {
1276
1940
  const p = `${root}/${sub}`;
1277
1941
  try {
1278
- if (existsSync2(p) && readdirSync(p).length > 0) return true;
1942
+ if (existsSync3(p) && readdirSync(p).length > 0) return true;
1279
1943
  } catch {
1280
1944
  }
1281
1945
  }
@@ -1285,17 +1949,17 @@ function hasPriorUsage() {
1285
1949
  }
1286
1950
  }
1287
1951
  function countJsonlLines(filePath) {
1288
- if (!existsSync2(filePath)) return 0;
1289
- return readFileSync2(filePath, "utf-8").split("\n").filter((l) => l.trim().length > 0).length;
1952
+ if (!existsSync3(filePath)) return 0;
1953
+ return readFileSync3(filePath, "utf-8").split("\n").filter((l) => l.trim().length > 0).length;
1290
1954
  }
1291
1955
  var NO_RULES = { count: 0, activeCount: 0, eligibleOutcomes: null, savedAt: null };
1292
1956
  function isActiveRule(rule) {
1293
1957
  return typeof rule === "object" && rule !== null && rule["status"] === "active";
1294
1958
  }
1295
1959
  function readRulesMetadata(filePath) {
1296
- if (!existsSync2(filePath)) return NO_RULES;
1960
+ if (!existsSync3(filePath)) return NO_RULES;
1297
1961
  try {
1298
- const raw = JSON.parse(readFileSync2(filePath, "utf-8"));
1962
+ const raw = JSON.parse(readFileSync3(filePath, "utf-8"));
1299
1963
  const rules = Array.isArray(raw["rules"]) ? raw["rules"] : [];
1300
1964
  const saved = raw["savedAt"];
1301
1965
  const eligible = raw["eligibleOutcomes"];
@@ -1310,7 +1974,7 @@ function readRulesMetadata(filePath) {
1310
1974
  }
1311
1975
  }
1312
1976
  function checkDirAccess(dir) {
1313
- const exists = existsSync2(dir);
1977
+ const exists = existsSync3(dir);
1314
1978
  if (!exists) return { exists: false, writable: false };
1315
1979
  try {
1316
1980
  accessSync(dir, fsConstants.W_OK);
@@ -1384,13 +2048,13 @@ async function checkSqlite() {
1384
2048
  }
1385
2049
  function checkDataDirectory() {
1386
2050
  const rootPath = getNexusDataDir();
1387
- const rootExists = existsSync2(rootPath);
2051
+ const rootExists = existsSync3(rootPath);
1388
2052
  const repoRoot = getNexusRepoDir();
1389
2053
  const perRepoSet = getPerRepoSubdirs();
1390
2054
  const subdirectories = DATA_SUBDIRECTORIES.map((name) => {
1391
2055
  const segments = name.split("/");
1392
2056
  const fullPath = nexusDataPath(...segments);
1393
- const exists = existsSync2(fullPath);
2057
+ const exists = existsSync3(fullPath);
1394
2058
  const scope = perRepoSet.has(segments[0] ?? "") ? "per-repo" : "cross-repo";
1395
2059
  return { name, path: fullPath, scope, exists, writable: exists && isWritable(fullPath) };
1396
2060
  });
@@ -1457,7 +2121,7 @@ function voterGatewayCostDeclaration(endpoint) {
1457
2121
  return resolveGatewayCostDeclaration(`api:${endpoint}`) ?? "no-default";
1458
2122
  }
1459
2123
  function isAllHealthy(input) {
1460
- return input.gateway !== "fail" && input.nodeSupported && input.hasAuthMethod && input.mcpServerReady && !installFreshnessFailsVerdict(input.installFreshness) && scratchSeverityIsAcceptable(worstSeverity(input.scratchSpace)) && // whenEmpty: zero detected CLIs is not a healthy install (#4581) — unless
2124
+ return input.gateway !== "fail" && input.nodeSupported && input.hasAuthMethod && input.mcpServerReady && input.configFile.error === void 0 && !installFreshnessFailsVerdict(input.installFreshness) && scratchSeverityIsAcceptable(worstSeverity(input.scratchSpace)) && // whenEmpty: zero detected CLIs is not a healthy install (#4581) — unless
1461
2125
  // a passing gateway serves the slots (#6609), as on a gateway-only host.
1462
2126
  allOf(
1463
2127
  input.clis,
@@ -1515,6 +2179,7 @@ async function runDoctor(deps = {}) {
1515
2179
  nodeSupported: nodeVersion.supported,
1516
2180
  hasAuthMethod: hasAnyAuthMethod(apiKeys, clis, gatewayVerdict(gateway)),
1517
2181
  mcpServerReady,
2182
+ configFile,
1518
2183
  installFreshness: env.installFreshness,
1519
2184
  scratchSpace: env.scratchSpace,
1520
2185
  clis,
@@ -1569,7 +2234,7 @@ async function runDoctorFix(result) {
1569
2234
  writeLine2("\u2500".repeat(40));
1570
2235
  let fixCount = 0;
1571
2236
  if (!result.dataDirectory.rootExists || result.dataDirectory.subdirectories.some((d) => !d.exists || !d.writable)) {
1572
- const { initDataDirectories: initDataDirectories2, describeDataDirFix: describeDataDirFix2 } = await import("./setup-data-dir-JQGH2JCH.js");
2237
+ const { initDataDirectories: initDataDirectories2, describeDataDirFix: describeDataDirFix2 } = await import("./setup-data-dir-75Z5PSSB.js");
1573
2238
  const outcome = describeDataDirFix2(initDataDirectories2());
1574
2239
  writeLine2(outcome.line);
1575
2240
  if (outcome.counted) fixCount++;
@@ -1626,7 +2291,7 @@ function initDataDirectories(dryRun = false) {
1626
2291
  return { success: true, rootPath: NEXUS_DATA_DIR, created, alreadyExisted, error: null };
1627
2292
  }
1628
2293
  function ensureDir(dirPath, dryRun, created, alreadyExisted, mode) {
1629
- if (existsSync3(dirPath)) {
2294
+ if (existsSync4(dirPath)) {
1630
2295
  alreadyExisted.push(dirPath);
1631
2296
  return;
1632
2297
  }
@@ -1655,6 +2320,21 @@ function describeDataDirFix(result) {
1655
2320
  }
1656
2321
 
1657
2322
  export {
2323
+ LoggingConfigSchema,
2324
+ ProviderConfigSchema,
2325
+ ModelTiersSchema,
2326
+ ModelConfigSchema,
2327
+ WorkflowConfigSchema,
2328
+ VALID_EXPERT_TIERS,
2329
+ VALID_EXPERT_DOMAINS,
2330
+ MAX_SYSTEM_PROMPT_LENGTH,
2331
+ CustomExpertDefinitionSchema,
2332
+ ExpertDefinitionSchema,
2333
+ ExpertConfigSchema,
2334
+ AppConfigSchema,
2335
+ defaultConfig,
2336
+ loadConfig,
2337
+ getConfig,
1658
2338
  DEFAULT_TASK_TTL_MS,
1659
2339
  clampTaskTtl,
1660
2340
  getCliAdapterDiagnostics,
@@ -1674,4 +2354,4 @@ export {
1674
2354
  startStdioServer,
1675
2355
  closeServer
1676
2356
  };
1677
- //# sourceMappingURL=chunk-W2XP4MEN.js.map
2357
+ //# sourceMappingURL=chunk-C6MM6Z65.js.map