@kadi.build/core 0.15.4 → 0.15.6

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.
package/src/config.ts CHANGED
@@ -103,9 +103,38 @@ export interface ConfigResult<T = Record<string, unknown>> {
103
103
  // File discovery
104
104
  // ═══════════════════════════════════════════════════════════════════════
105
105
 
106
+ /**
107
+ * Check whether a directory is inside an `abilities/` folder.
108
+ *
109
+ * When an ability is installed as a dependency (e.g.
110
+ * `project/abilities/auth-ability@0.1.0/`), it lives inside an
111
+ * `abilities/` directory and should NOT use its own config.yml —
112
+ * the project's config.yml (one or more levels above `abilities/`)
113
+ * is the correct one.
114
+ *
115
+ * When the same ability runs standalone (deployed directly, no
116
+ * `abilities/` ancestor), its local config.yml is the right choice.
117
+ */
118
+ function isInsideAbilitiesDir(dir: string): boolean {
119
+ // Walk up a few levels looking for a parent named 'abilities'.
120
+ // Limit depth to avoid scanning the entire filesystem.
121
+ let current = dir;
122
+ for (let i = 0; i < 4; i++) {
123
+ const parent = path.dirname(current);
124
+ if (parent === current) break; // filesystem root
125
+ if (path.basename(parent) === 'abilities') return true;
126
+ current = parent;
127
+ }
128
+ return false;
129
+ }
130
+
106
131
  /**
107
132
  * Walk up the directory tree looking for a file.
108
133
  *
134
+ * If the search starts inside an `abilities/` folder (indicating the
135
+ * caller is an installed sub-component), config files found at that
136
+ * level are skipped so the walk continues to the project root.
137
+ *
109
138
  * @param filename - File to search for (default: `'config.yml'`)
110
139
  * @param startDir - Directory to start from (default: `process.cwd()`)
111
140
  * @returns Absolute path if found, `null` otherwise.
@@ -116,12 +145,24 @@ export function findConfigFile(
116
145
  ): string | null {
117
146
  let dir = path.resolve(startDir ?? process.cwd());
118
147
 
148
+ // Detect whether we started inside an abilities/ directory.
149
+ // If so, skip config files at the starting level and inside abilities/.
150
+ const startedInAbilities = isInsideAbilitiesDir(dir);
151
+
119
152
  // eslint-disable-next-line no-constant-condition
120
153
  while (true) {
121
154
  const candidate = path.join(dir, filename);
122
155
  try {
123
156
  if (fs.statSync(candidate).isFile()) {
124
- return candidate;
157
+ // If we started inside abilities/, skip config files that
158
+ // are still within the abilities/ tree (the sub-component's
159
+ // own defaults). Once we've walked above abilities/, the
160
+ // next match is the project config — use it.
161
+ if (startedInAbilities && isInsideAbilitiesDir(dir)) {
162
+ // Skip this candidate and keep walking
163
+ } else {
164
+ return candidate;
165
+ }
125
166
  }
126
167
  } catch {
127
168
  // Not found, keep walking
package/src/index.ts CHANGED
@@ -31,6 +31,26 @@ export { z } from 'zod';
31
31
  // Main client
32
32
  export { KadiClient } from './client.js';
33
33
 
34
+ // ReActLoop — core agent execution primitive
35
+ export { ReActLoop } from './react-loop.js';
36
+ export type {
37
+ ReActConfig,
38
+ ReActResult,
39
+ TurnInfo,
40
+ TurnResult,
41
+ LoopHandle,
42
+ ModelConfig,
43
+ ToolsConfig,
44
+ CustomTool,
45
+ ToolRouter,
46
+ EventsConfig,
47
+ ReActHooks,
48
+ Message,
49
+ ToolCall,
50
+ ModelResponse,
51
+ StopReason,
52
+ } from './react-loop.js';
53
+
34
54
  // Errors
35
55
  export { KadiError } from './errors.js';
36
56
  export type { ErrorCode, ErrorContext } from './errors.js';