@kadi.build/core 0.12.0 → 0.14.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.
@@ -0,0 +1,113 @@
1
+ /**
2
+ * Configuration loader for KADI agents and abilities.
3
+ *
4
+ * Provides standardized config.yml discovery with a 2-tier fallback:
5
+ * 1. **Project-level** — walk up from startDir looking for config.yml
6
+ * 2. **Global-level** — ~/.kadi/config.yml (shared KADI infrastructure settings)
7
+ *
8
+ * Resolution order per-field (highest-priority first):
9
+ * 1. Environment variables (PREFIX_KEY)
10
+ * 2. Project config.yml section
11
+ * 3. Global config.yml section (~/.kadi/config.yml)
12
+ * 4. Built-in defaults
13
+ *
14
+ * Secrets (API keys, tokens) should NEVER appear in config.yml.
15
+ * Use secret-ability / kadi-secret for credentials.
16
+ *
17
+ * @example
18
+ * ```typescript
19
+ * import { loadConfig } from '@kadi.build/core';
20
+ *
21
+ * const { config, configPath, source } = loadConfig({
22
+ * section: 'memory',
23
+ * envPrefix: 'MEMORY',
24
+ * defaults: { database: 'kadi_memory', embedding_model: 'text-embedding-3-small' },
25
+ * });
26
+ * ```
27
+ *
28
+ * TypeScript equivalent of: kadi-core-py/src/kadi/config.py
29
+ */
30
+ /** How the config file was discovered. */
31
+ export type ConfigSource = 'project' | 'global' | 'default';
32
+ /** Options for {@link loadConfig}. */
33
+ export interface LoadConfigOptions {
34
+ /**
35
+ * YAML section key to extract (e.g. `'tunnel'`, `'memory'`, `'kadi_agent'`).
36
+ * The top-level key in config.yml whose value becomes the config object.
37
+ */
38
+ section: string;
39
+ /**
40
+ * Directory to start the upward walk from.
41
+ * @default process.cwd()
42
+ */
43
+ startDir?: string;
44
+ /**
45
+ * Config filename to search for.
46
+ * @default 'config.yml'
47
+ */
48
+ filename?: string;
49
+ /**
50
+ * Environment variable prefix for automatic overrides.
51
+ *
52
+ * When set, loadConfig will scan `process.env` for keys starting with
53
+ * `PREFIX_` and merge them into the config (highest priority).
54
+ *
55
+ * Mapping: `PREFIX_KEY_NAME` → config field `key_name` (lowercased).
56
+ *
57
+ * @example envPrefix: 'MEMORY' → MEMORY_DATABASE overrides config.database
58
+ */
59
+ envPrefix?: string;
60
+ /**
61
+ * Built-in defaults (lowest priority).
62
+ * These are used when neither env vars nor config.yml provide a value.
63
+ */
64
+ defaults?: Record<string, unknown>;
65
+ }
66
+ /** Result of {@link loadConfig}. */
67
+ export interface ConfigResult<T = Record<string, unknown>> {
68
+ /** Merged config object: env > project config.yml > global config.yml > defaults. */
69
+ config: T;
70
+ /** Absolute path to the config.yml that was loaded, or null if none found. */
71
+ configPath: string | null;
72
+ /** Where the primary config was found. */
73
+ source: ConfigSource;
74
+ }
75
+ /**
76
+ * Walk up the directory tree looking for a file.
77
+ *
78
+ * @param filename - File to search for (default: `'config.yml'`)
79
+ * @param startDir - Directory to start from (default: `process.cwd()`)
80
+ * @returns Absolute path if found, `null` otherwise.
81
+ */
82
+ export declare function findConfigFile(filename?: string, startDir?: string): string | null;
83
+ /**
84
+ * Find the global KADI config file.
85
+ *
86
+ * @param filename - Config filename (default: `'config.yml'`)
87
+ * @returns Absolute path to `~/.kadi/<filename>` if it exists, `null` otherwise.
88
+ */
89
+ export declare function findGlobalConfigFile(filename?: string): string | null;
90
+ /**
91
+ * Load configuration for a KADI agent or ability.
92
+ *
93
+ * Resolution order per-field (highest-priority first):
94
+ * 1. Environment variables (`envPrefix` + `_KEY`)
95
+ * 2. Project `config.yml` section (walk-up from startDir)
96
+ * 3. Global `~/.kadi/config.yml` section
97
+ * 4. Built-in `defaults`
98
+ *
99
+ * @typeParam T - The shape of the merged config object.
100
+ * @param options - Configuration loading options.
101
+ * @returns Merged config with metadata about where it was found.
102
+ *
103
+ * @example
104
+ * ```typescript
105
+ * const { config } = loadConfig<TunnelConfig>({
106
+ * section: 'tunnel',
107
+ * envPrefix: 'KADI_TUNNEL',
108
+ * defaults: { default_service: 'kadi', server_port: 7000 },
109
+ * });
110
+ * ```
111
+ */
112
+ export declare function loadConfig<T = Record<string, unknown>>(options: LoadConfigOptions): ConfigResult<T>;
113
+ //# sourceMappingURL=config.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AAuDH,0CAA0C;AAC1C,MAAM,MAAM,YAAY,GAAG,SAAS,GAAG,QAAQ,GAAG,SAAS,CAAC;AAE5D,sCAAsC;AACtC,MAAM,WAAW,iBAAiB;IAChC;;;OAGG;IACH,OAAO,EAAE,MAAM,CAAC;IAEhB;;;OAGG;IACH,QAAQ,CAAC,EAAE,MAAM,CAAC;IAElB;;;OAGG;IACH,QAAQ,CAAC,EAAE,MAAM,CAAC;IAElB;;;;;;;;;OASG;IACH,SAAS,CAAC,EAAE,MAAM,CAAC;IAEnB;;;OAGG;IACH,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CACpC;AAED,oCAAoC;AACpC,MAAM,WAAW,YAAY,CAAC,CAAC,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC;IACvD,qFAAqF;IACrF,MAAM,EAAE,CAAC,CAAC;IACV,8EAA8E;IAC9E,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,0CAA0C;IAC1C,MAAM,EAAE,YAAY,CAAC;CACtB;AAMD;;;;;;GAMG;AACH,wBAAgB,cAAc,CAC5B,QAAQ,GAAE,MAAyB,EACnC,QAAQ,CAAC,EAAE,MAAM,GAChB,MAAM,GAAG,IAAI,CAmBf;AAED;;;;;GAKG;AACH,wBAAgB,oBAAoB,CAClC,QAAQ,GAAE,MAAyB,GAClC,MAAM,GAAG,IAAI,CAUf;AAgFD;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,wBAAgB,UAAU,CAAC,CAAC,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EACpD,OAAO,EAAE,iBAAiB,GACzB,YAAY,CAAC,CAAC,CAAC,CAsDjB"}
package/dist/config.js ADDED
@@ -0,0 +1,271 @@
1
+ /**
2
+ * Configuration loader for KADI agents and abilities.
3
+ *
4
+ * Provides standardized config.yml discovery with a 2-tier fallback:
5
+ * 1. **Project-level** — walk up from startDir looking for config.yml
6
+ * 2. **Global-level** — ~/.kadi/config.yml (shared KADI infrastructure settings)
7
+ *
8
+ * Resolution order per-field (highest-priority first):
9
+ * 1. Environment variables (PREFIX_KEY)
10
+ * 2. Project config.yml section
11
+ * 3. Global config.yml section (~/.kadi/config.yml)
12
+ * 4. Built-in defaults
13
+ *
14
+ * Secrets (API keys, tokens) should NEVER appear in config.yml.
15
+ * Use secret-ability / kadi-secret for credentials.
16
+ *
17
+ * @example
18
+ * ```typescript
19
+ * import { loadConfig } from '@kadi.build/core';
20
+ *
21
+ * const { config, configPath, source } = loadConfig({
22
+ * section: 'memory',
23
+ * envPrefix: 'MEMORY',
24
+ * defaults: { database: 'kadi_memory', embedding_model: 'text-embedding-3-small' },
25
+ * });
26
+ * ```
27
+ *
28
+ * TypeScript equivalent of: kadi-core-py/src/kadi/config.py
29
+ */
30
+ import * as fs from 'node:fs';
31
+ import * as path from 'node:path';
32
+ import * as os from 'node:os';
33
+ // ═══════════════════════════════════════════════════════════════════════
34
+ // YAML import — optional runtime dependency
35
+ // ═══════════════════════════════════════════════════════════════════════
36
+ /**
37
+ * We lazy-import js-yaml so that kadi-core does NOT require it as a hard
38
+ * dependency. Consumers that use loadConfig() need js-yaml installed;
39
+ * everything else in kadi-core works without it.
40
+ *
41
+ * In practice every KADI agent/ability already has js-yaml, so this is
42
+ * a formality rather than a real constraint.
43
+ */
44
+ let yaml = null;
45
+ let yamlResolved = false;
46
+ function requireYaml() {
47
+ if (!yamlResolved) {
48
+ yamlResolved = true;
49
+ try {
50
+ // eslint-disable-next-line @typescript-eslint/no-require-imports
51
+ yaml = require('js-yaml');
52
+ }
53
+ catch {
54
+ try {
55
+ // ESM fallback — createRequire for pure-ESM environments
56
+ const { createRequire } = require('node:module');
57
+ const localRequire = createRequire(process.cwd() + '/');
58
+ yaml = localRequire('js-yaml');
59
+ }
60
+ catch {
61
+ yaml = null;
62
+ }
63
+ }
64
+ }
65
+ return yaml;
66
+ }
67
+ // ═══════════════════════════════════════════════════════════════════════
68
+ // Constants
69
+ // ═══════════════════════════════════════════════════════════════════════
70
+ /** Default config filename. */
71
+ const DEFAULT_FILENAME = 'config.yml';
72
+ /** Global KADI config directory. */
73
+ const GLOBAL_KADI_DIR = path.join(os.homedir(), '.kadi');
74
+ // ═══════════════════════════════════════════════════════════════════════
75
+ // File discovery
76
+ // ═══════════════════════════════════════════════════════════════════════
77
+ /**
78
+ * Walk up the directory tree looking for a file.
79
+ *
80
+ * @param filename - File to search for (default: `'config.yml'`)
81
+ * @param startDir - Directory to start from (default: `process.cwd()`)
82
+ * @returns Absolute path if found, `null` otherwise.
83
+ */
84
+ export function findConfigFile(filename = DEFAULT_FILENAME, startDir) {
85
+ let dir = path.resolve(startDir ?? process.cwd());
86
+ // eslint-disable-next-line no-constant-condition
87
+ while (true) {
88
+ const candidate = path.join(dir, filename);
89
+ try {
90
+ if (fs.statSync(candidate).isFile()) {
91
+ return candidate;
92
+ }
93
+ }
94
+ catch {
95
+ // Not found, keep walking
96
+ }
97
+ const parent = path.dirname(dir);
98
+ if (parent === dir)
99
+ break; // Filesystem root
100
+ dir = parent;
101
+ }
102
+ return null;
103
+ }
104
+ /**
105
+ * Find the global KADI config file.
106
+ *
107
+ * @param filename - Config filename (default: `'config.yml'`)
108
+ * @returns Absolute path to `~/.kadi/<filename>` if it exists, `null` otherwise.
109
+ */
110
+ export function findGlobalConfigFile(filename = DEFAULT_FILENAME) {
111
+ const globalPath = path.join(GLOBAL_KADI_DIR, filename);
112
+ try {
113
+ if (fs.statSync(globalPath).isFile()) {
114
+ return globalPath;
115
+ }
116
+ }
117
+ catch {
118
+ // Not found
119
+ }
120
+ return null;
121
+ }
122
+ // ═══════════════════════════════════════════════════════════════════════
123
+ // Section extraction
124
+ // ═══════════════════════════════════════════════════════════════════════
125
+ /**
126
+ * Parse a YAML file and extract a top-level section.
127
+ */
128
+ function readSection(filePath, section) {
129
+ const lib = requireYaml();
130
+ if (!lib)
131
+ return {};
132
+ try {
133
+ const content = fs.readFileSync(filePath, 'utf8');
134
+ const parsed = lib.load(content);
135
+ if (!parsed || typeof parsed !== 'object')
136
+ return {};
137
+ const sectionData = parsed[section];
138
+ if (!sectionData || typeof sectionData !== 'object')
139
+ return {};
140
+ return sectionData;
141
+ }
142
+ catch {
143
+ return {};
144
+ }
145
+ }
146
+ // ═══════════════════════════════════════════════════════════════════════
147
+ // Environment variable overlay
148
+ // ═══════════════════════════════════════════════════════════════════════
149
+ /**
150
+ * Build an overrides object from environment variables matching a prefix.
151
+ *
152
+ * `PREFIX_KEY_NAME=value` → `{ key_name: value }`
153
+ *
154
+ * Numeric strings are auto-coerced to numbers.
155
+ * `'true'` / `'false'` are coerced to booleans.
156
+ */
157
+ function envOverrides(prefix, knownKeys) {
158
+ const result = {};
159
+ const prefixUpper = prefix.toUpperCase() + '_';
160
+ for (const [envKey, envVal] of Object.entries(process.env)) {
161
+ if (!envKey.startsWith(prefixUpper) || envVal === undefined)
162
+ continue;
163
+ const configKey = envKey.slice(prefixUpper.length).toLowerCase();
164
+ // Skip if we have a known-keys set and this key isn't in it
165
+ if (knownKeys && !knownKeys.has(configKey))
166
+ continue;
167
+ result[configKey] = coerce(envVal);
168
+ }
169
+ return result;
170
+ }
171
+ /**
172
+ * Coerce a string environment value to its natural JS type.
173
+ */
174
+ function coerce(value) {
175
+ if (value === 'true')
176
+ return true;
177
+ if (value === 'false')
178
+ return false;
179
+ if (value === '')
180
+ return value;
181
+ // Attempt numeric coercion only for things that look numeric
182
+ const num = Number(value);
183
+ if (!Number.isNaN(num) && value.trim() !== '')
184
+ return num;
185
+ return value;
186
+ }
187
+ // ═══════════════════════════════════════════════════════════════════════
188
+ // Main API
189
+ // ═══════════════════════════════════════════════════════════════════════
190
+ /**
191
+ * Load configuration for a KADI agent or ability.
192
+ *
193
+ * Resolution order per-field (highest-priority first):
194
+ * 1. Environment variables (`envPrefix` + `_KEY`)
195
+ * 2. Project `config.yml` section (walk-up from startDir)
196
+ * 3. Global `~/.kadi/config.yml` section
197
+ * 4. Built-in `defaults`
198
+ *
199
+ * @typeParam T - The shape of the merged config object.
200
+ * @param options - Configuration loading options.
201
+ * @returns Merged config with metadata about where it was found.
202
+ *
203
+ * @example
204
+ * ```typescript
205
+ * const { config } = loadConfig<TunnelConfig>({
206
+ * section: 'tunnel',
207
+ * envPrefix: 'KADI_TUNNEL',
208
+ * defaults: { default_service: 'kadi', server_port: 7000 },
209
+ * });
210
+ * ```
211
+ */
212
+ export function loadConfig(options) {
213
+ const { section, startDir, filename = DEFAULT_FILENAME, envPrefix, defaults = {}, } = options;
214
+ // ── Layer 4: Defaults ──────────────────────────────────────────────
215
+ let merged = { ...defaults };
216
+ // ── Layer 3: Global config.yml (~/.kadi/config.yml) ────────────────
217
+ let configPath = null;
218
+ let source = 'default';
219
+ const globalPath = findGlobalConfigFile(filename);
220
+ if (globalPath) {
221
+ const globalSection = readSection(globalPath, section);
222
+ if (Object.keys(globalSection).length > 0) {
223
+ merged = shallowMerge(merged, globalSection);
224
+ configPath = globalPath;
225
+ source = 'global';
226
+ }
227
+ }
228
+ // ── Layer 2: Project config.yml (walk-up) ──────────────────────────
229
+ const projectPath = findConfigFile(filename, startDir);
230
+ if (projectPath) {
231
+ // Only use project config if it's not the same file as global
232
+ const isDistinct = !globalPath || path.resolve(projectPath) !== path.resolve(globalPath);
233
+ if (isDistinct) {
234
+ const projectSection = readSection(projectPath, section);
235
+ if (Object.keys(projectSection).length > 0) {
236
+ merged = shallowMerge(merged, projectSection);
237
+ configPath = projectPath;
238
+ source = 'project';
239
+ }
240
+ }
241
+ }
242
+ // ── Layer 1: Environment variables (highest priority) ──────────────
243
+ if (envPrefix) {
244
+ const env = envOverrides(envPrefix);
245
+ if (Object.keys(env).length > 0) {
246
+ merged = shallowMerge(merged, env);
247
+ }
248
+ }
249
+ return {
250
+ config: merged,
251
+ configPath,
252
+ source,
253
+ };
254
+ }
255
+ // ═══════════════════════════════════════════════════════════════════════
256
+ // Helpers
257
+ // ═══════════════════════════════════════════════════════════════════════
258
+ /**
259
+ * Shallow merge — source keys overwrite target keys.
260
+ * Only copies defined (non-undefined) values.
261
+ */
262
+ function shallowMerge(target, source) {
263
+ const result = { ...target };
264
+ for (const [key, val] of Object.entries(source)) {
265
+ if (val !== undefined) {
266
+ result[key] = val;
267
+ }
268
+ }
269
+ return result;
270
+ }
271
+ //# sourceMappingURL=config.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"config.js","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"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"}
package/dist/index.d.ts CHANGED
@@ -32,6 +32,8 @@ export type { ClientConfig, BrokerEntry, ResolvedConfig, ToolDefinition, BrokerT
32
32
  export { AgentJsonManager } from './agent-json.js';
33
33
  export { ProcessManager, ManagedProcess } from './process-manager.js';
34
34
  export { getByPath, setByPath, deleteByPath, deepMerge } from './utils.js';
35
+ export { loadConfig, findConfigFile, findGlobalConfigFile } from './config.js';
36
+ export type { LoadConfigOptions, ConfigResult, ConfigSource } from './config.js';
35
37
  export { StdioMessageReader, StdioMessageWriter } from './stdio-framing.js';
36
38
  export { zodToJsonSchema, isZodSchema } from './zod.js';
37
39
  export { findProjectRoot, readLockFile, findAbilityEntry, resolveAbilityPath, resolveAbilityEntry, getInstalledAbilityNames, } from './lockfile.js';
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AAGH,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAGxB,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAGzC,OAAO,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AACxC,YAAY,EAAE,SAAS,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAG3D,YAAY,EAEV,YAAY,EACZ,WAAW,EACX,cAAc,EAGd,cAAc,EACd,oBAAoB,EACpB,iBAAiB,EACjB,WAAW,EACX,cAAc,EACd,UAAU,EACV,cAAc,EAGd,aAAa,EACb,aAAa,EACb,aAAa,EACb,YAAY,EAGZ,mBAAmB,EACnB,WAAW,EACX,iBAAiB,EACjB,gBAAgB,EAChB,iBAAiB,EACjB,mBAAmB,EAGnB,WAAW,EACX,YAAY,EACZ,cAAc,EACd,iBAAiB,EAGjB,WAAW,EACX,kBAAkB,EAClB,cAAc,EACd,gBAAgB,EAChB,WAAW,EAGX,cAAc,EACd,eAAe,EACf,mBAAmB,EACnB,YAAY,EACZ,cAAc,EAGd,aAAa,EACb,gBAAgB,EAGhB,cAAc,EAGd,SAAS,EACT,gBAAgB,EAChB,qBAAqB,EACrB,sBAAsB,EACtB,uBAAuB,EACvB,kBAAkB,EAClB,WAAW,EACX,cAAc,EAGd,WAAW,EACX,YAAY,EACZ,YAAY,EACZ,WAAW,EACX,eAAe,EACf,aAAa,EACb,kBAAkB,EAClB,mBAAmB,GACpB,MAAM,YAAY,CAAC;AAGpB,OAAO,EAAE,gBAAgB,EAAE,MAAM,iBAAiB,CAAC;AAGnD,OAAO,EAAE,cAAc,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AAGtE,OAAO,EAAE,SAAS,EAAE,SAAS,EAAE,YAAY,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AAG3E,OAAO,EAAE,kBAAkB,EAAE,kBAAkB,EAAE,MAAM,oBAAoB,CAAC;AAG5E,OAAO,EAAE,eAAe,EAAE,WAAW,EAAE,MAAM,UAAU,CAAC;AAGxD,OAAO,EACL,eAAe,EACf,YAAY,EACZ,gBAAgB,EAChB,kBAAkB,EAClB,mBAAmB,EACnB,wBAAwB,GACzB,MAAM,eAAe,CAAC;AAGvB,OAAO,EAAE,mBAAmB,EAAE,MAAM,wBAAwB,CAAC;AAC7D,OAAO,EAAE,kBAAkB,EAAE,MAAM,uBAAuB,CAAC;AAC3D,OAAO,EAAE,mBAAmB,EAAE,MAAM,wBAAwB,CAAC;AAC7D,YAAY,EAAE,sBAAsB,EAAE,MAAM,wBAAwB,CAAC;AAGrE,OAAO,KAAK,QAAQ,MAAM,eAAe,CAAC;AAG1C,OAAO,EAAE,sBAAsB,EAAE,0BAA0B,EAAE,MAAM,aAAa,CAAC;AACjF,YAAY,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AAGH,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAGxB,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAGzC,OAAO,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AACxC,YAAY,EAAE,SAAS,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAG3D,YAAY,EAEV,YAAY,EACZ,WAAW,EACX,cAAc,EAGd,cAAc,EACd,oBAAoB,EACpB,iBAAiB,EACjB,WAAW,EACX,cAAc,EACd,UAAU,EACV,cAAc,EAGd,aAAa,EACb,aAAa,EACb,aAAa,EACb,YAAY,EAGZ,mBAAmB,EACnB,WAAW,EACX,iBAAiB,EACjB,gBAAgB,EAChB,iBAAiB,EACjB,mBAAmB,EAGnB,WAAW,EACX,YAAY,EACZ,cAAc,EACd,iBAAiB,EAGjB,WAAW,EACX,kBAAkB,EAClB,cAAc,EACd,gBAAgB,EAChB,WAAW,EAGX,cAAc,EACd,eAAe,EACf,mBAAmB,EACnB,YAAY,EACZ,cAAc,EAGd,aAAa,EACb,gBAAgB,EAGhB,cAAc,EAGd,SAAS,EACT,gBAAgB,EAChB,qBAAqB,EACrB,sBAAsB,EACtB,uBAAuB,EACvB,kBAAkB,EAClB,WAAW,EACX,cAAc,EAGd,WAAW,EACX,YAAY,EACZ,YAAY,EACZ,WAAW,EACX,eAAe,EACf,aAAa,EACb,kBAAkB,EAClB,mBAAmB,GACpB,MAAM,YAAY,CAAC;AAGpB,OAAO,EAAE,gBAAgB,EAAE,MAAM,iBAAiB,CAAC;AAGnD,OAAO,EAAE,cAAc,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AAGtE,OAAO,EAAE,SAAS,EAAE,SAAS,EAAE,YAAY,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AAG3E,OAAO,EAAE,UAAU,EAAE,cAAc,EAAE,oBAAoB,EAAE,MAAM,aAAa,CAAC;AAC/E,YAAY,EAAE,iBAAiB,EAAE,YAAY,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAGjF,OAAO,EAAE,kBAAkB,EAAE,kBAAkB,EAAE,MAAM,oBAAoB,CAAC;AAG5E,OAAO,EAAE,eAAe,EAAE,WAAW,EAAE,MAAM,UAAU,CAAC;AAGxD,OAAO,EACL,eAAe,EACf,YAAY,EACZ,gBAAgB,EAChB,kBAAkB,EAClB,mBAAmB,EACnB,wBAAwB,GACzB,MAAM,eAAe,CAAC;AAGvB,OAAO,EAAE,mBAAmB,EAAE,MAAM,wBAAwB,CAAC;AAC7D,OAAO,EAAE,kBAAkB,EAAE,MAAM,uBAAuB,CAAC;AAC3D,OAAO,EAAE,mBAAmB,EAAE,MAAM,wBAAwB,CAAC;AAC7D,YAAY,EAAE,sBAAsB,EAAE,MAAM,wBAAwB,CAAC;AAGrE,OAAO,KAAK,QAAQ,MAAM,eAAe,CAAC;AAG1C,OAAO,EAAE,sBAAsB,EAAE,0BAA0B,EAAE,MAAM,aAAa,CAAC;AACjF,YAAY,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAC"}
package/dist/index.js CHANGED
@@ -36,6 +36,8 @@ export { AgentJsonManager } from './agent-json.js';
36
36
  export { ProcessManager, ManagedProcess } from './process-manager.js';
37
37
  // Dot-path utilities
38
38
  export { getByPath, setByPath, deleteByPath, deepMerge } from './utils.js';
39
+ // Config loader (walk-up config.yml + ~/.kadi/ global fallback)
40
+ export { loadConfig, findConfigFile, findGlobalConfigFile } from './config.js';
39
41
  // Stdio framing (for advanced use cases — building custom bridge protocols)
40
42
  export { StdioMessageReader, StdioMessageWriter } from './stdio-framing.js';
41
43
  // Zod utilities
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AAEH,uCAAuC;AACvC,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB,cAAc;AACd,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAEzC,SAAS;AACT,OAAO,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AAiFxC,qBAAqB;AACrB,OAAO,EAAE,gBAAgB,EAAE,MAAM,iBAAiB,CAAC;AAEnD,kBAAkB;AAClB,OAAO,EAAE,cAAc,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AAEtE,qBAAqB;AACrB,OAAO,EAAE,SAAS,EAAE,SAAS,EAAE,YAAY,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AAE3E,4EAA4E;AAC5E,OAAO,EAAE,kBAAkB,EAAE,kBAAkB,EAAE,MAAM,oBAAoB,CAAC;AAE5E,gBAAgB;AAChB,OAAO,EAAE,eAAe,EAAE,WAAW,EAAE,MAAM,UAAU,CAAC;AAExD,sBAAsB;AACtB,OAAO,EACL,eAAe,EACf,YAAY,EACZ,gBAAgB,EAChB,kBAAkB,EAClB,mBAAmB,EACnB,wBAAwB,GACzB,MAAM,eAAe,CAAC;AAEvB,sCAAsC;AACtC,OAAO,EAAE,mBAAmB,EAAE,MAAM,wBAAwB,CAAC;AAC7D,OAAO,EAAE,kBAAkB,EAAE,MAAM,uBAAuB,CAAC;AAC3D,OAAO,EAAE,mBAAmB,EAAE,MAAM,wBAAwB,CAAC;AAG7D,4BAA4B;AAC5B,OAAO,KAAK,QAAQ,MAAM,eAAe,CAAC;AAE1C,kDAAkD;AAClD,OAAO,EAAE,sBAAsB,EAAE,0BAA0B,EAAE,MAAM,aAAa,CAAC"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AAEH,uCAAuC;AACvC,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB,cAAc;AACd,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAEzC,SAAS;AACT,OAAO,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AAiFxC,qBAAqB;AACrB,OAAO,EAAE,gBAAgB,EAAE,MAAM,iBAAiB,CAAC;AAEnD,kBAAkB;AAClB,OAAO,EAAE,cAAc,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AAEtE,qBAAqB;AACrB,OAAO,EAAE,SAAS,EAAE,SAAS,EAAE,YAAY,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AAE3E,gEAAgE;AAChE,OAAO,EAAE,UAAU,EAAE,cAAc,EAAE,oBAAoB,EAAE,MAAM,aAAa,CAAC;AAG/E,4EAA4E;AAC5E,OAAO,EAAE,kBAAkB,EAAE,kBAAkB,EAAE,MAAM,oBAAoB,CAAC;AAE5E,gBAAgB;AAChB,OAAO,EAAE,eAAe,EAAE,WAAW,EAAE,MAAM,UAAU,CAAC;AAExD,sBAAsB;AACtB,OAAO,EACL,eAAe,EACf,YAAY,EACZ,gBAAgB,EAChB,kBAAkB,EAClB,mBAAmB,EACnB,wBAAwB,GACzB,MAAM,eAAe,CAAC;AAEvB,sCAAsC;AACtC,OAAO,EAAE,mBAAmB,EAAE,MAAM,wBAAwB,CAAC;AAC7D,OAAO,EAAE,kBAAkB,EAAE,MAAM,uBAAuB,CAAC;AAC3D,OAAO,EAAE,mBAAmB,EAAE,MAAM,wBAAwB,CAAC;AAG7D,4BAA4B;AAC5B,OAAO,KAAK,QAAQ,MAAM,eAAe,CAAC;AAE1C,kDAAkD;AAClD,OAAO,EAAE,sBAAsB,EAAE,0BAA0B,EAAE,MAAM,aAAa,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kadi.build/core",
3
- "version": "0.12.0",
3
+ "version": "0.14.0",
4
4
  "description": "A lean, readable SDK for building KADI agents.",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
package/src/client.ts CHANGED
@@ -206,6 +206,9 @@ export class KadiClient {
206
206
  /** Registered disconnect hooks */
207
207
  private readonly disconnectHooks: Array<() => Promise<void>> = [];
208
208
 
209
+ /** Registered reconnect hooks */
210
+ private readonly reconnectHooks: Array<(brokerName: string) => void | Promise<void>> = [];
211
+
209
212
  /** Lazy-initialized AgentJsonManager */
210
213
  private _agentJson: AgentJsonManager | null = null;
211
214
 
@@ -1698,9 +1701,36 @@ export class KadiClient {
1698
1701
  // Finalize connection (heartbeat + status)
1699
1702
  this.finalizeConnection(state);
1700
1703
 
1704
+ // Re-subscribe event patterns that were active before disconnect.
1705
+ // cleanupBroker() cleared subscribedPatterns but preserved eventHandlers,
1706
+ // so we replay broker-side subscriptions for every pattern that still has handlers.
1707
+ if (state.eventHandlers.size > 0) {
1708
+ const patterns = [...state.eventHandlers.keys()];
1709
+ console.error(`[KADI] Re-subscribing ${patterns.length} event pattern(s) on broker "${state.name}"`);
1710
+ for (const pattern of patterns) {
1711
+ try {
1712
+ await this.sendRequest(state, protocol.eventSubscribe(this.nextRequestId++, pattern));
1713
+ state.subscribedPatterns.add(pattern);
1714
+ } catch (err) {
1715
+ const msg = err instanceof Error ? err.message : String(err);
1716
+ console.error(`[KADI] Failed to re-subscribe "${pattern}" on broker "${state.name}": ${msg}`);
1717
+ }
1718
+ }
1719
+ }
1720
+
1701
1721
  // Success!
1702
1722
  console.error(`[KADI] Reconnected to broker "${state.name}" after ${state.reconnectAttempts} attempts`);
1703
1723
  state.reconnectAttempts = 0;
1724
+
1725
+ // Fire reconnect hooks (best-effort, don't let a failing hook break reconnect)
1726
+ for (const hook of this.reconnectHooks) {
1727
+ try {
1728
+ await hook(state.name);
1729
+ } catch (err) {
1730
+ const msg = err instanceof Error ? err.message : String(err);
1731
+ console.error(`[KADI] Reconnect hook error for broker "${state.name}": ${msg}`);
1732
+ }
1733
+ }
1704
1734
  } catch (error) {
1705
1735
  // Log the error and try again
1706
1736
  const message = error instanceof Error ? error.message : String(error);
@@ -1722,6 +1752,26 @@ export class KadiClient {
1722
1752
  this.disconnectHooks.push(hook);
1723
1753
  }
1724
1754
 
1755
+ /**
1756
+ * Register a hook to run after a successful broker reconnection.
1757
+ *
1758
+ * Event subscriptions are automatically re-established on reconnect,
1759
+ * so most agents won't need this. Use it for additional recovery logic:
1760
+ * re-fetching state, logging, sending a "I'm back" message, etc.
1761
+ *
1762
+ * @param hook - Called with the broker name after each successful reconnect.
1763
+ *
1764
+ * @example
1765
+ * ```typescript
1766
+ * client.onReconnect(async (brokerName) => {
1767
+ * console.log(`Reconnected to ${brokerName} — re-initializing state`);
1768
+ * });
1769
+ * ```
1770
+ */
1771
+ onReconnect(hook: (brokerName: string) => void | Promise<void>): void {
1772
+ this.reconnectHooks.push(hook);
1773
+ }
1774
+
1725
1775
  /**
1726
1776
  * Disconnect from broker(s) and run registered cleanup hooks.
1727
1777
  *