@kadi.build/core 0.0.1-alpha.13 → 0.0.1-alpha.14
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/dist/abilities/AbilityLoader.d.ts +136 -12
- package/dist/abilities/AbilityLoader.d.ts.map +1 -1
- package/dist/abilities/AbilityLoader.js +432 -64
- package/dist/abilities/AbilityLoader.js.map +1 -1
- package/dist/abilities/types.d.ts +119 -4
- package/dist/abilities/types.d.ts.map +1 -1
- package/dist/api/index.d.ts +1 -40
- package/dist/api/index.d.ts.map +1 -1
- package/dist/api/index.js +0 -57
- package/dist/api/index.js.map +1 -1
- package/dist/client/KadiClient.d.ts.map +1 -1
- package/dist/client/KadiClient.js +65 -4
- package/dist/client/KadiClient.js.map +1 -1
- package/dist/config/ConfigLoader.d.ts.map +1 -1
- package/dist/config/ConfigLoader.js +1 -0
- package/dist/config/ConfigLoader.js.map +1 -1
- package/dist/config/ConfigResolver.d.ts.map +1 -1
- package/dist/config/ConfigResolver.js +1 -0
- package/dist/config/ConfigResolver.js.map +1 -1
- package/dist/index.d.ts +2 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -1
- package/dist/transports/NativeTransport.d.ts.map +1 -1
- package/dist/transports/NativeTransport.js +22 -0
- package/dist/transports/NativeTransport.js.map +1 -1
- package/dist/transports/StdioTransport.d.ts +17 -2
- package/dist/transports/StdioTransport.d.ts.map +1 -1
- package/dist/transports/StdioTransport.js +45 -10
- package/dist/transports/StdioTransport.js.map +1 -1
- package/dist/types/config.d.ts +48 -33
- package/dist/types/config.d.ts.map +1 -1
- package/dist/types/config.js.map +1 -1
- package/dist/types/index.d.ts +1 -1
- package/dist/types/index.d.ts.map +1 -1
- package/dist/types/index.js.map +1 -1
- package/dist/utils/LockfileResolver.d.ts +169 -28
- package/dist/utils/LockfileResolver.d.ts.map +1 -1
- package/dist/utils/LockfileResolver.js +325 -71
- package/dist/utils/LockfileResolver.js.map +1 -1
- package/dist/utils/legacyHelpers.d.ts +82 -0
- package/dist/utils/legacyHelpers.d.ts.map +1 -0
- package/dist/utils/legacyHelpers.js +226 -0
- package/dist/utils/legacyHelpers.js.map +1 -0
- package/package.json +1 -1
|
@@ -17,7 +17,8 @@ import { AbilityProxy } from './AbilityProxy.js';
|
|
|
17
17
|
import { AbilityCache } from './AbilityCache.js';
|
|
18
18
|
import { AbilityValidator } from './AbilityValidator.js';
|
|
19
19
|
import { resolveAbilityConfig } from '../utils/LockfileResolver.js';
|
|
20
|
-
import
|
|
20
|
+
import { join } from 'path';
|
|
21
|
+
import { readFileSync } from 'fs';
|
|
21
22
|
/**
|
|
22
23
|
* Ability Loader
|
|
23
24
|
*
|
|
@@ -59,6 +60,45 @@ export class AbilityLoader {
|
|
|
59
60
|
* - Same name, different brokers
|
|
60
61
|
*/
|
|
61
62
|
cache = new AbilityCache();
|
|
63
|
+
/**
|
|
64
|
+
* Explicit project root (provided by KadiClient)
|
|
65
|
+
*
|
|
66
|
+
* This is the directory where KADI will look for abilities.
|
|
67
|
+
* - If provided: use this directory
|
|
68
|
+
* - If not provided: default to process.cwd()
|
|
69
|
+
*
|
|
70
|
+
* This makes context explicit instead of inferred.
|
|
71
|
+
*/
|
|
72
|
+
explicitProjectRoot;
|
|
73
|
+
/**
|
|
74
|
+
* Create an AbilityLoader
|
|
75
|
+
*
|
|
76
|
+
* **AbilityLoader** is responsible for finding, loading, and caching abilities.
|
|
77
|
+
*
|
|
78
|
+
* @param options - Loader options
|
|
79
|
+
* @param options.projectRoot - Explicit project root (overrides detection)
|
|
80
|
+
*
|
|
81
|
+
* @example
|
|
82
|
+
* ```typescript
|
|
83
|
+
* // With explicit project root (CLI plugin)
|
|
84
|
+
* const loader = new AbilityLoader({
|
|
85
|
+
* projectRoot: '/opt/homebrew/lib/node_modules/@kadi.build/cli'
|
|
86
|
+
* });
|
|
87
|
+
*
|
|
88
|
+
* // Without explicit project root (user project)
|
|
89
|
+
* const loader = new AbilityLoader({});
|
|
90
|
+
* ```
|
|
91
|
+
*/
|
|
92
|
+
constructor(options) {
|
|
93
|
+
// Store the explicit project root for later use
|
|
94
|
+
// This will be used in detectProjectRoot() method
|
|
95
|
+
this.explicitProjectRoot = options?.projectRoot;
|
|
96
|
+
if (process.env.KADI_DEBUG) {
|
|
97
|
+
console.log('[DEBUG] AbilityLoader created:', {
|
|
98
|
+
explicitProjectRoot: this.explicitProjectRoot || 'none (will use default)'
|
|
99
|
+
});
|
|
100
|
+
}
|
|
101
|
+
}
|
|
62
102
|
/**
|
|
63
103
|
* Load an ability with explicit transport
|
|
64
104
|
*
|
|
@@ -95,7 +135,19 @@ export class AbilityLoader {
|
|
|
95
135
|
* ```
|
|
96
136
|
*/
|
|
97
137
|
async load(name, transport, options = {}, brokerClient) {
|
|
98
|
-
// Step 1:
|
|
138
|
+
// Step 1: Detect project root
|
|
139
|
+
// This tells us where to look for abilities
|
|
140
|
+
// - CLI plugins: Uses explicitProjectRoot from constructor
|
|
141
|
+
// - User projects: Uses process.cwd()
|
|
142
|
+
const projectRoot = this.detectProjectRoot();
|
|
143
|
+
if (process.env.KADI_DEBUG) {
|
|
144
|
+
console.log('[DEBUG] Loading ability with project root:', {
|
|
145
|
+
abilityName: name,
|
|
146
|
+
transport,
|
|
147
|
+
projectRoot
|
|
148
|
+
});
|
|
149
|
+
}
|
|
150
|
+
// Step 2: Check cache first (avoid duplicate loading)
|
|
99
151
|
const cacheKey = {
|
|
100
152
|
name,
|
|
101
153
|
transport,
|
|
@@ -107,17 +159,19 @@ export class AbilityLoader {
|
|
|
107
159
|
// Cache hit! Return existing ability
|
|
108
160
|
return cached;
|
|
109
161
|
}
|
|
110
|
-
// Step
|
|
111
|
-
|
|
112
|
-
//
|
|
162
|
+
// Step 3: Create appropriate transport with project root context
|
|
163
|
+
// The transport will use projectRoot to find the ability
|
|
164
|
+
const transportInstance = this.createTransport(name, transport, options, projectRoot, // ← Pass projectRoot (was callerFilePath before)
|
|
165
|
+
brokerClient);
|
|
166
|
+
// Step 4: Connect to ability
|
|
113
167
|
await transportInstance.connect();
|
|
114
|
-
// Step
|
|
168
|
+
// Step 5: Create proxy for natural method calling
|
|
115
169
|
const ability = AbilityProxy.create(name, transportInstance, transport);
|
|
116
|
-
// Step
|
|
170
|
+
// Step 6: Validate if requested
|
|
117
171
|
if (options.validate) {
|
|
118
172
|
AbilityValidator.validate(ability);
|
|
119
173
|
}
|
|
120
|
-
// Step
|
|
174
|
+
// Step 7: Cache for future use
|
|
121
175
|
this.cache.set(cacheKey, ability);
|
|
122
176
|
return ability;
|
|
123
177
|
}
|
|
@@ -188,29 +242,122 @@ export class AbilityLoader {
|
|
|
188
242
|
cleanup() {
|
|
189
243
|
return this.cache.cleanup();
|
|
190
244
|
}
|
|
245
|
+
/**
|
|
246
|
+
* Detect project root directory
|
|
247
|
+
*
|
|
248
|
+
* **Strategy** (simple and explicit):
|
|
249
|
+
* 1. Use explicit override (constructor option or env var)
|
|
250
|
+
* 2. Fall back to process.cwd() (default behavior, like professor's approach)
|
|
251
|
+
*
|
|
252
|
+
* **Why This Approach?**
|
|
253
|
+
* - **Explicit over implicit**: Plugins tell us where to load from
|
|
254
|
+
* - **Simple**: No stack parsing, no complex inference
|
|
255
|
+
* - **Reliable**: Works in all environments (dev, prod, bundlers, etc.)
|
|
256
|
+
* - **Escape hatch**: Env var for edge cases
|
|
257
|
+
*
|
|
258
|
+
* **How It's Used**:
|
|
259
|
+
* - CLI plugins: Pass `ctx.core.getKadiInstallPath()` via constructor
|
|
260
|
+
* - User projects: Don't pass anything, uses process.cwd()
|
|
261
|
+
* - Edge cases: Set KADI_PROJECT_ROOT env var
|
|
262
|
+
*
|
|
263
|
+
* @returns Detected project root directory
|
|
264
|
+
*
|
|
265
|
+
* @example
|
|
266
|
+
* ```typescript
|
|
267
|
+
* // Example 1: CLI plugin (explicit)
|
|
268
|
+
* const loader = new AbilityLoader({
|
|
269
|
+
* projectRoot: '/opt/homebrew/lib/node_modules/@kadi.build/cli'
|
|
270
|
+
* });
|
|
271
|
+
* const root = loader.detectProjectRoot();
|
|
272
|
+
* // Returns: '/opt/homebrew/lib/node_modules/@kadi.build/cli'
|
|
273
|
+
*
|
|
274
|
+
* // Example 2: User project (default)
|
|
275
|
+
* const loader = new AbilityLoader({});
|
|
276
|
+
* const root = loader.detectProjectRoot();
|
|
277
|
+
* // Returns: process.cwd() (e.g., '/Users/kassi/Repositories/KADI/gateway-agent')
|
|
278
|
+
*
|
|
279
|
+
* // Example 3: Env var override (escape hatch)
|
|
280
|
+
* process.env.KADI_PROJECT_ROOT = '/app';
|
|
281
|
+
* const loader = new AbilityLoader({});
|
|
282
|
+
* const root = loader.detectProjectRoot();
|
|
283
|
+
* // Returns: '/app'
|
|
284
|
+
* ```
|
|
285
|
+
*/
|
|
286
|
+
detectProjectRoot() {
|
|
287
|
+
// ====================================================================
|
|
288
|
+
// Layer 1: Explicit Override
|
|
289
|
+
// ====================================================================
|
|
290
|
+
// Priority: Constructor option > Environment variable
|
|
291
|
+
//
|
|
292
|
+
// This gives us full control over where abilities are loaded from.
|
|
293
|
+
// - CLI plugins use constructor option (explicit)
|
|
294
|
+
// - Edge cases use env var (escape hatch)
|
|
295
|
+
if (this.explicitProjectRoot) {
|
|
296
|
+
// Constructor option provided
|
|
297
|
+
// This happens when CLI plugins pass ctx.core.getKadiInstallPath()
|
|
298
|
+
if (process.env.KADI_DEBUG) {
|
|
299
|
+
console.log('[DEBUG] Using explicit project root from constructor:', this.explicitProjectRoot);
|
|
300
|
+
}
|
|
301
|
+
return this.explicitProjectRoot;
|
|
302
|
+
}
|
|
303
|
+
const envRoot = process.env.KADI_PROJECT_ROOT;
|
|
304
|
+
if (envRoot) {
|
|
305
|
+
// Environment variable set
|
|
306
|
+
// This is the escape hatch for edge cases
|
|
307
|
+
if (process.env.KADI_DEBUG) {
|
|
308
|
+
console.log('[DEBUG] Using explicit project root from env:', envRoot);
|
|
309
|
+
}
|
|
310
|
+
return envRoot;
|
|
311
|
+
}
|
|
312
|
+
// ====================================================================
|
|
313
|
+
// Layer 2: Default Behavior
|
|
314
|
+
// ====================================================================
|
|
315
|
+
// Use process.cwd() (current working directory)
|
|
316
|
+
//
|
|
317
|
+
// This is the default behavior for user projects.
|
|
318
|
+
// It works the same way as professor's original approach.
|
|
319
|
+
//
|
|
320
|
+
// When you run: node src/server.js
|
|
321
|
+
// - process.cwd() returns the directory you ran the command from
|
|
322
|
+
// - Usually this is the project root
|
|
323
|
+
// - Abilities are in {cwd}/abilities/
|
|
324
|
+
const cwd = process.cwd();
|
|
325
|
+
if (process.env.KADI_DEBUG) {
|
|
326
|
+
console.log('[DEBUG] Using default project root (process.cwd):', cwd);
|
|
327
|
+
}
|
|
328
|
+
return cwd;
|
|
329
|
+
}
|
|
191
330
|
/**
|
|
192
331
|
* Create appropriate transport instance
|
|
193
332
|
*
|
|
194
|
-
*
|
|
195
|
-
* Each transport type has its own
|
|
333
|
+
* Factory pattern that creates the right transport based on type.
|
|
334
|
+
* Each transport type (native, stdio, broker) has its own creation method.
|
|
196
335
|
*
|
|
197
336
|
* @param name - Ability name
|
|
198
|
-
* @param transport - Transport type (
|
|
199
|
-
* @param options - Load options
|
|
337
|
+
* @param transport - Transport type (native, stdio, or broker)
|
|
338
|
+
* @param options - Load options (transport-specific)
|
|
200
339
|
* @param brokerClient - Broker client (for broker transport)
|
|
340
|
+
* @param projectRoot - Project root directory (where to find abilities)
|
|
201
341
|
* @returns Transport instance ready to connect
|
|
202
342
|
*
|
|
203
343
|
* @throws {KadiError} If transport creation fails
|
|
204
344
|
*/
|
|
205
|
-
createTransport(name, transport, options,
|
|
345
|
+
createTransport(name, transport, options, projectRoot, // ← Changed from callerFilePath?: string (moved before optional params)
|
|
346
|
+
brokerClient) {
|
|
206
347
|
// Delegate to appropriate transport creation method
|
|
348
|
+
// Pass projectRoot to enable resolution from correct project location
|
|
207
349
|
switch (transport) {
|
|
208
350
|
case 'broker':
|
|
351
|
+
// Broker transport doesn't need projectRoot (abilities are remote)
|
|
209
352
|
return this.createBrokerTransport(name, options, brokerClient);
|
|
210
353
|
case 'native':
|
|
211
|
-
|
|
354
|
+
// Native transport loads abilities from local filesystem
|
|
355
|
+
// Needs projectRoot to find abilities/ folder
|
|
356
|
+
return this.createNativeTransport(name, options, projectRoot);
|
|
212
357
|
case 'stdio':
|
|
213
|
-
|
|
358
|
+
// Stdio transport runs abilities as child processes
|
|
359
|
+
// Needs projectRoot to find ability executables
|
|
360
|
+
return this.createStdioTransport(name, options, projectRoot);
|
|
214
361
|
default:
|
|
215
362
|
// TypeScript exhaustiveness check ensures this is unreachable
|
|
216
363
|
const never = transport;
|
|
@@ -262,35 +409,52 @@ export class AbilityLoader {
|
|
|
262
409
|
}, brokerClient);
|
|
263
410
|
}
|
|
264
411
|
/**
|
|
265
|
-
* Create native transport with
|
|
412
|
+
* Create native transport with project root context
|
|
266
413
|
*
|
|
267
|
-
*
|
|
268
|
-
*
|
|
414
|
+
* Native transport loads abilities directly from the filesystem.
|
|
415
|
+
*
|
|
416
|
+
* Resolution strategy:
|
|
417
|
+
* 1. If explicit path provided → use it directly
|
|
418
|
+
* 2. Otherwise → auto-resolve from projectRoot using lockfile
|
|
269
419
|
*
|
|
270
420
|
* @param name - Ability name
|
|
271
421
|
* @param options - Load options (native-specific)
|
|
422
|
+
* @param projectRoot - Project root directory (from detectProjectRoot)
|
|
272
423
|
* @returns Native transport instance
|
|
273
424
|
*
|
|
274
|
-
* @throws {KadiError} If path
|
|
425
|
+
* @throws {KadiError} If path cannot be resolved
|
|
275
426
|
*/
|
|
276
|
-
createNativeTransport(name, options
|
|
427
|
+
createNativeTransport(name, options, projectRoot // ← Changed from callerFilePath?: string
|
|
428
|
+
) {
|
|
277
429
|
// Step 1: Extract native-specific options (type-safe!)
|
|
278
430
|
const nativeOptions = options;
|
|
279
|
-
// Step 2: Determine ability path
|
|
431
|
+
// Step 2: Determine ability path and version
|
|
280
432
|
let abilityPath;
|
|
281
433
|
let abilityVersion = options.version;
|
|
282
434
|
if (nativeOptions.path) {
|
|
283
|
-
// Explicit path provided - use it
|
|
435
|
+
// Explicit path provided - use it directly
|
|
436
|
+
// This allows users to override auto-resolution if needed
|
|
284
437
|
abilityPath = nativeOptions.path;
|
|
438
|
+
if (process.env.KADI_DEBUG) {
|
|
439
|
+
console.log('[DEBUG] Using explicit ability path:', abilityPath);
|
|
440
|
+
}
|
|
285
441
|
}
|
|
286
442
|
else {
|
|
287
|
-
// No path provided - auto-resolve
|
|
443
|
+
// No path provided - auto-resolve using project root
|
|
288
444
|
try {
|
|
289
|
-
|
|
445
|
+
// Pass projectRoot directly to resolver
|
|
446
|
+
// Resolver will look in {projectRoot}/agent-lock.json
|
|
447
|
+
const config = resolveAbilityConfig(name, projectRoot);
|
|
290
448
|
abilityPath = config.path;
|
|
291
449
|
abilityVersion = config.version;
|
|
292
|
-
|
|
293
|
-
|
|
450
|
+
if (process.env.KADI_DEBUG) {
|
|
451
|
+
console.log('[DEBUG] Auto-resolved ability path:', {
|
|
452
|
+
name,
|
|
453
|
+
projectRoot,
|
|
454
|
+
resolvedPath: abilityPath,
|
|
455
|
+
version: abilityVersion
|
|
456
|
+
});
|
|
457
|
+
}
|
|
294
458
|
}
|
|
295
459
|
catch (error) {
|
|
296
460
|
// Re-throw lockfile resolution errors with context
|
|
@@ -300,100 +464,304 @@ export class AbilityLoader {
|
|
|
300
464
|
throw new KadiError(`Failed to auto-resolve ability '${name}'`, ErrorCode.ABILITY_NOT_FOUND, 404, {
|
|
301
465
|
abilityName: name,
|
|
302
466
|
transport: 'native',
|
|
303
|
-
|
|
467
|
+
projectRoot,
|
|
468
|
+
reason: 'Could not auto-resolve path from project root',
|
|
304
469
|
originalError: error instanceof Error ? error.message : String(error),
|
|
305
470
|
suggestion: 'Provide explicit path or run `kadi install`',
|
|
306
471
|
example: 'await client.load("calculator", "native", { path: "./abilities/calculator" })',
|
|
307
472
|
alternatives: [
|
|
308
473
|
'Install ability: kadi install ' + name,
|
|
309
474
|
'Provide explicit path in options',
|
|
310
|
-
'Check that agent-lock.json exists'
|
|
475
|
+
'Check that agent-lock.json exists in project root'
|
|
311
476
|
]
|
|
312
477
|
});
|
|
313
478
|
}
|
|
314
479
|
}
|
|
315
|
-
// Step 3:
|
|
480
|
+
// Step 3: Read agent.json to get entrypoint
|
|
481
|
+
// Abilities can specify their entry file in agent.json (e.g., dist/kadi-ability.js)
|
|
482
|
+
let entryPoint = 'index.js'; // Default fallback
|
|
483
|
+
try {
|
|
484
|
+
const agentJsonPath = join(abilityPath, 'agent.json');
|
|
485
|
+
const agentJson = JSON.parse(readFileSync(agentJsonPath, 'utf8'));
|
|
486
|
+
if (agentJson.entrypoint) {
|
|
487
|
+
entryPoint = agentJson.entrypoint;
|
|
488
|
+
}
|
|
489
|
+
}
|
|
490
|
+
catch (error) {
|
|
491
|
+
// If agent.json doesn't exist or can't be read, use default
|
|
492
|
+
// This maintains backwards compatibility with abilities that don't have agent.json
|
|
493
|
+
}
|
|
494
|
+
// Step 4: Create NativeTransport with resolved path and entrypoint
|
|
316
495
|
return new NativeTransport({
|
|
317
496
|
abilityName: name,
|
|
318
497
|
abilityPath,
|
|
319
498
|
abilityVersion,
|
|
499
|
+
entryPoint,
|
|
320
500
|
timeout: options.timeout
|
|
321
|
-
// entryPoint defaults to 'index.js' in NativeTransport constructor
|
|
322
501
|
});
|
|
323
502
|
}
|
|
324
503
|
/**
|
|
325
|
-
* Create stdio transport with command
|
|
504
|
+
* Create stdio transport with context-aware command resolution
|
|
505
|
+
*
|
|
506
|
+
* Resolution strategy:
|
|
507
|
+
* 1. If explicit command provided → use it directly
|
|
508
|
+
* 2. Otherwise → auto-resolve from caller's project context
|
|
509
|
+
*
|
|
510
|
+
* **Context-Aware Resolution:**
|
|
511
|
+
* - Uses callerFilePath to find the correct project root
|
|
512
|
+
* - CLI plugins resolve from CLI's abilities/
|
|
513
|
+
* - User projects resolve from project's abilities/
|
|
514
|
+
*
|
|
515
|
+
* @param name - Ability name
|
|
516
|
+
* @param options - Load options (stdio-specific)
|
|
517
|
+
* @param callerFilePath - File path where load() was called from
|
|
518
|
+
* @returns Stdio transport instance
|
|
519
|
+
*
|
|
520
|
+
* @throws {KadiError} If command cannot be resolved
|
|
521
|
+
*/
|
|
522
|
+
/**
|
|
523
|
+
* Create stdio transport with auto-resolution support
|
|
524
|
+
*
|
|
525
|
+
* **Two modes of operation**:
|
|
326
526
|
*
|
|
327
|
-
*
|
|
328
|
-
*
|
|
527
|
+
* 1. **Auto-resolution** (no command provided):
|
|
528
|
+
* - Resolves ability path using resolveAbilityConfig()
|
|
529
|
+
* - Reads ability's agent.json
|
|
530
|
+
* - Extracts scripts.start command
|
|
531
|
+
* - Spawns child process with shell: true
|
|
532
|
+
* - Sets cwd to ability directory
|
|
533
|
+
* - Injects KADI_* environment variables
|
|
534
|
+
*
|
|
535
|
+
* 2. **Explicit command** (command provided):
|
|
536
|
+
* - Uses provided command/args directly
|
|
537
|
+
* - Developer has full control
|
|
538
|
+
* - Still injects KADI_* environment variables
|
|
329
539
|
*
|
|
330
540
|
* @param name - Ability name
|
|
331
541
|
* @param options - Load options (stdio-specific)
|
|
542
|
+
* @param projectRoot - Project root directory (from detectProjectRoot)
|
|
332
543
|
* @returns Stdio transport instance
|
|
333
544
|
*
|
|
334
|
-
* @throws {KadiError} If command
|
|
545
|
+
* @throws {KadiError} If command cannot be resolved or scripts.start missing
|
|
546
|
+
*
|
|
547
|
+
* @example
|
|
548
|
+
* ```typescript
|
|
549
|
+
* // Auto-resolution mode
|
|
550
|
+
* const transport = this.createStdioTransport('calculator', {}, '/project/root');
|
|
551
|
+
* // Resolves path, reads agent.json, uses scripts.start
|
|
552
|
+
*
|
|
553
|
+
* // Explicit mode
|
|
554
|
+
* const transport = this.createStdioTransport('calculator', {
|
|
555
|
+
* command: 'node',
|
|
556
|
+
* args: ['dist/index.js']
|
|
557
|
+
* }, '/project/root');
|
|
558
|
+
* ```
|
|
335
559
|
*/
|
|
336
|
-
createStdioTransport(name, options) {
|
|
337
|
-
//
|
|
560
|
+
createStdioTransport(name, options, projectRoot) {
|
|
561
|
+
// ====================================================================
|
|
562
|
+
// STEP 1: Extract stdio-specific options (type-safe!)
|
|
563
|
+
// ====================================================================
|
|
338
564
|
const stdioOptions = options;
|
|
339
|
-
//
|
|
565
|
+
// ====================================================================
|
|
566
|
+
// STEP 2: Declare variables for transport configuration
|
|
567
|
+
// ====================================================================
|
|
568
|
+
// Variables that may be reassigned based on resolution mode
|
|
340
569
|
let command;
|
|
341
570
|
let args = stdioOptions.args;
|
|
342
571
|
let cwd = stdioOptions.cwd;
|
|
343
572
|
let abilityVersion = options.version;
|
|
573
|
+
let shellMode = false; // Whether to use shell: true
|
|
574
|
+
// Variables that are only read (never reassigned)
|
|
575
|
+
const env = stdioOptions.env;
|
|
576
|
+
// ====================================================================
|
|
577
|
+
// STEP 3: Determine if we're using explicit command or auto-resolution
|
|
578
|
+
// ====================================================================
|
|
344
579
|
if (stdioOptions.command) {
|
|
345
|
-
//
|
|
580
|
+
// ==================================================================
|
|
581
|
+
// EXPLICIT COMMAND MODE
|
|
582
|
+
// ==================================================================
|
|
583
|
+
// Developer provided command explicitly - use it directly
|
|
584
|
+
// This gives developers full control when they need it
|
|
346
585
|
command = stdioOptions.command;
|
|
586
|
+
if (process.env.KADI_DEBUG) {
|
|
587
|
+
console.log('[DEBUG] Using explicit stdio command:', {
|
|
588
|
+
abilityName: name,
|
|
589
|
+
command,
|
|
590
|
+
args,
|
|
591
|
+
cwd: cwd || 'default (process.cwd())'
|
|
592
|
+
});
|
|
593
|
+
}
|
|
347
594
|
}
|
|
348
595
|
else {
|
|
349
|
-
//
|
|
596
|
+
// ==================================================================
|
|
597
|
+
// AUTO-RESOLUTION MODE
|
|
598
|
+
// ==================================================================
|
|
599
|
+
// No command provided - auto-resolve from ability's agent.json
|
|
600
|
+
if (process.env.KADI_DEBUG) {
|
|
601
|
+
console.log('[DEBUG] Auto-resolving stdio command for ability:', name);
|
|
602
|
+
}
|
|
350
603
|
try {
|
|
351
|
-
|
|
352
|
-
//
|
|
353
|
-
//
|
|
354
|
-
//
|
|
355
|
-
// -
|
|
356
|
-
//
|
|
357
|
-
|
|
358
|
-
//
|
|
359
|
-
const
|
|
360
|
-
|
|
361
|
-
|
|
604
|
+
// ================================================================
|
|
605
|
+
// STEP 3a: Resolve ability path and metadata
|
|
606
|
+
// ================================================================
|
|
607
|
+
// This function already handles:
|
|
608
|
+
// - Checking agent-lock.json (if exists)
|
|
609
|
+
// - Scanning abilities/ folder
|
|
610
|
+
// - Picking highest version
|
|
611
|
+
// - Reading agent.json manifest
|
|
612
|
+
const config = resolveAbilityConfig(name, projectRoot);
|
|
613
|
+
if (process.env.KADI_DEBUG) {
|
|
614
|
+
console.log('[DEBUG] Resolved ability config:', {
|
|
615
|
+
name,
|
|
616
|
+
path: config.path,
|
|
617
|
+
version: config.version,
|
|
618
|
+
entry: config.entry
|
|
619
|
+
});
|
|
620
|
+
}
|
|
621
|
+
// ================================================================
|
|
622
|
+
// STEP 3b: Extract scripts.start from manifest
|
|
623
|
+
// ================================================================
|
|
624
|
+
const startScript = config.manifest.scripts?.start;
|
|
625
|
+
// ================================================================
|
|
626
|
+
// STEP 3c: Validate that scripts.start exists
|
|
627
|
+
// ================================================================
|
|
628
|
+
if (!startScript || startScript.trim() === '') {
|
|
629
|
+
// scripts.start is missing or empty - cannot auto-resolve
|
|
630
|
+
// Throw helpful error with actionable suggestions
|
|
631
|
+
throw new KadiError(`Cannot auto-load ability '${name}' via stdio - missing scripts.start in agent.json`, ErrorCode.INVALID_CONFIG, 400, {
|
|
632
|
+
abilityName: name,
|
|
633
|
+
version: config.version,
|
|
634
|
+
abilityPath: config.path,
|
|
635
|
+
agentJsonPath: join(config.path, 'agent.json'),
|
|
636
|
+
reason: 'agent.json does not have a "scripts.start" field',
|
|
637
|
+
suggestion: 'Add scripts.start to agent.json OR provide explicit command options',
|
|
638
|
+
// Detailed fix instructions
|
|
639
|
+
fix: {
|
|
640
|
+
option1: {
|
|
641
|
+
title: 'Add scripts.start to agent.json',
|
|
642
|
+
description: `Edit: ${join(config.path, 'agent.json')}`,
|
|
643
|
+
example: {
|
|
644
|
+
name: name,
|
|
645
|
+
version: config.version,
|
|
646
|
+
scripts: {
|
|
647
|
+
start: 'node dist/index.js'
|
|
648
|
+
}
|
|
649
|
+
},
|
|
650
|
+
instructions: 'Add a "scripts" section with a "start" command that runs your ability'
|
|
651
|
+
},
|
|
652
|
+
option2: {
|
|
653
|
+
title: 'Use explicit command options',
|
|
654
|
+
description: 'Provide command and args when loading',
|
|
655
|
+
example: `await client.load('${name}', 'stdio', {\n command: 'node',\n args: ['${config.entry}']\n})`
|
|
656
|
+
}
|
|
657
|
+
},
|
|
658
|
+
// Examples of valid scripts.start values
|
|
659
|
+
examples: [
|
|
660
|
+
'"node dist/index.js" - Run Node.js file',
|
|
661
|
+
'"npm start" - Run npm script',
|
|
662
|
+
'"python3 main.py" - Run Python script',
|
|
663
|
+
'"npm run build && npm start" - Complex shell command'
|
|
664
|
+
],
|
|
665
|
+
// Alternative approaches
|
|
666
|
+
alternatives: [
|
|
667
|
+
`Add "scripts": { "start": "node ${config.entry}" } to agent.json`,
|
|
668
|
+
`Use explicit options: client.load('${name}', 'stdio', { command: 'node', args: ['${config.entry}'] })`
|
|
669
|
+
],
|
|
670
|
+
hint: 'scripts.start is the command used to run your ability when loaded via stdio transport'
|
|
671
|
+
});
|
|
672
|
+
}
|
|
673
|
+
// ================================================================
|
|
674
|
+
// STEP 3d: Use scripts.start as the command
|
|
675
|
+
// ================================================================
|
|
676
|
+
// We'll run this with shell: true so it supports:
|
|
677
|
+
// - Complex commands: "npm run build && npm start"
|
|
678
|
+
// - Pipes: "cat input.txt | process"
|
|
679
|
+
// - Environment variables: "PORT=3000 node server.js"
|
|
680
|
+
command = startScript;
|
|
681
|
+
shellMode = true; // Enable shell mode for complex commands
|
|
682
|
+
// ================================================================
|
|
683
|
+
// STEP 3e: Set working directory to ability's directory
|
|
684
|
+
// ================================================================
|
|
685
|
+
// This ensures relative paths in scripts.start work correctly
|
|
686
|
+
// Example: If scripts.start is "node dist/index.js"
|
|
687
|
+
// and cwd is "/abilities/calculator/1.0.0/"
|
|
688
|
+
// then it will find "/abilities/calculator/1.0.0/dist/index.js"
|
|
362
689
|
cwd = config.path;
|
|
363
|
-
//
|
|
690
|
+
// ================================================================
|
|
691
|
+
// STEP 3f: Store version from resolved config
|
|
692
|
+
// ================================================================
|
|
364
693
|
abilityVersion = config.version;
|
|
365
|
-
//
|
|
366
|
-
//
|
|
694
|
+
// ================================================================
|
|
695
|
+
// STEP 3g: Clear args (not used in shell mode - command contains everything)
|
|
696
|
+
// ================================================================
|
|
697
|
+
args = undefined;
|
|
698
|
+
if (process.env.KADI_DEBUG) {
|
|
699
|
+
console.log('[DEBUG] Auto-resolved stdio command:', {
|
|
700
|
+
abilityName: name,
|
|
701
|
+
command: startScript,
|
|
702
|
+
cwd,
|
|
703
|
+
version: abilityVersion,
|
|
704
|
+
shellMode: true
|
|
705
|
+
});
|
|
706
|
+
}
|
|
367
707
|
}
|
|
368
708
|
catch (error) {
|
|
369
|
-
// Re-throw
|
|
709
|
+
// Re-throw KadiError as-is (already has helpful context)
|
|
370
710
|
if (error instanceof KadiError) {
|
|
371
711
|
throw error;
|
|
372
712
|
}
|
|
373
|
-
|
|
713
|
+
// Wrap unexpected errors with context
|
|
714
|
+
throw new KadiError(`Failed to auto-resolve stdio command for ability '${name}'`, ErrorCode.ABILITY_NOT_FOUND, 404, {
|
|
374
715
|
abilityName: name,
|
|
375
716
|
transport: 'stdio',
|
|
376
|
-
|
|
717
|
+
projectRoot,
|
|
718
|
+
reason: 'Could not auto-resolve command from ability manifest',
|
|
377
719
|
originalError: error instanceof Error ? error.message : String(error),
|
|
378
|
-
suggestion: 'Provide explicit command or
|
|
379
|
-
example: 'await client.load("calculator", "stdio", { command: "node", args: ["./service.js"] })',
|
|
720
|
+
suggestion: 'Provide explicit command or check ability installation',
|
|
380
721
|
alternatives: [
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
'Check that agent
|
|
722
|
+
`Install ability: kadi install ${name}`,
|
|
723
|
+
`Use explicit command: client.load('${name}', 'stdio', { command: '...', args: [...] })`,
|
|
724
|
+
'Check that agent.json exists in ability directory'
|
|
384
725
|
]
|
|
385
726
|
});
|
|
386
727
|
}
|
|
387
728
|
}
|
|
388
|
-
//
|
|
729
|
+
// ====================================================================
|
|
730
|
+
// STEP 4: Prepare environment variables
|
|
731
|
+
// ====================================================================
|
|
732
|
+
// Merge developer's env vars with auto-injected KADI_* variables
|
|
733
|
+
const mergedEnv = {
|
|
734
|
+
// Inherit parent process environment (important for PATH, etc.)
|
|
735
|
+
...process.env,
|
|
736
|
+
// Auto-inject KADI-specific variables
|
|
737
|
+
// These help the child process understand its context
|
|
738
|
+
KADI_MODE: 'stdio',
|
|
739
|
+
KADI_ABILITY_NAME: name,
|
|
740
|
+
KADI_ABILITY_VERSION: abilityVersion || 'unknown',
|
|
741
|
+
// Developer's custom environment variables (if provided)
|
|
742
|
+
// These override defaults if there are conflicts
|
|
743
|
+
...(env || {})
|
|
744
|
+
};
|
|
745
|
+
if (process.env.KADI_DEBUG) {
|
|
746
|
+
console.log('[DEBUG] Stdio transport environment variables:', {
|
|
747
|
+
KADI_MODE: mergedEnv.KADI_MODE,
|
|
748
|
+
KADI_ABILITY_NAME: mergedEnv.KADI_ABILITY_NAME,
|
|
749
|
+
KADI_ABILITY_VERSION: mergedEnv.KADI_ABILITY_VERSION,
|
|
750
|
+
customEnvVars: Object.keys(env || {})
|
|
751
|
+
});
|
|
752
|
+
}
|
|
753
|
+
// ====================================================================
|
|
754
|
+
// STEP 5: Create StdioTransport with resolved/provided configuration
|
|
755
|
+
// ====================================================================
|
|
389
756
|
return new StdioTransport({
|
|
390
757
|
abilityName: name,
|
|
391
758
|
command,
|
|
392
759
|
args,
|
|
393
760
|
cwd,
|
|
394
|
-
env:
|
|
761
|
+
env: mergedEnv,
|
|
395
762
|
timeout: options.timeout,
|
|
396
|
-
abilityVersion
|
|
763
|
+
abilityVersion,
|
|
764
|
+
shellMode // Pass shell mode flag to transport
|
|
397
765
|
});
|
|
398
766
|
}
|
|
399
767
|
}
|