keystone-cli 0.6.0 ā 0.7.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.
- package/README.md +34 -0
- package/package.json +1 -1
- package/src/cli.ts +233 -21
- package/src/db/memory-db.ts +6 -0
- package/src/db/sqlite-setup.test.ts +47 -0
- package/src/db/workflow-db.ts +6 -0
- package/src/expression/evaluator.ts +2 -0
- package/src/parser/schema.ts +3 -0
- package/src/runner/debug-repl.test.ts +240 -6
- package/src/runner/llm-adapter.test.ts +10 -4
- package/src/runner/llm-executor.ts +39 -3
- package/src/runner/shell-executor.ts +40 -12
- package/src/runner/standard-tools-integration.test.ts +147 -0
- package/src/runner/standard-tools.test.ts +69 -0
- package/src/runner/standard-tools.ts +270 -0
- package/src/runner/step-executor.test.ts +194 -1
- package/src/runner/step-executor.ts +46 -15
- package/src/runner/stream-utils.test.ts +113 -7
- package/src/runner/stream-utils.ts +4 -4
- package/src/runner/workflow-runner.ts +14 -20
- package/src/templates/agents/keystone-architect.md +16 -2
- package/src/templates/agents/software-engineer.md +17 -0
- package/src/templates/memory-service.yaml +54 -0
- package/src/templates/robust-automation.yaml +44 -0
- package/src/templates/scaffold-feature.yaml +1 -0
package/README.md
CHANGED
|
@@ -260,6 +260,23 @@ finally:
|
|
|
260
260
|
type: shell
|
|
261
261
|
run: echo "Workflow finished"
|
|
262
262
|
|
|
263
|
+
### Expression Syntax
|
|
264
|
+
|
|
265
|
+
Keystone uses `${{ }}` syntax for dynamic values. Expressions are evaluated using a safe AST parser.
|
|
266
|
+
|
|
267
|
+
- `${{ inputs.name }}`: Access workflow inputs.
|
|
268
|
+
- `${{ steps.id.output }}`: Access the raw output of a previous step.
|
|
269
|
+
- `${{ steps.id.outputs.field }}`: Access specific fields if the output is an object.
|
|
270
|
+
- `${{ steps.id.status }}`: Get the execution status of a step (`'success'`, `'failed'`, etc.).
|
|
271
|
+
- `${{ item }}`: Access the current item in a `foreach` loop.
|
|
272
|
+
- `${{ args.name }}`: Access tool arguments (available ONLY inside agent tool execution steps).
|
|
273
|
+
- `${{ secrets.NAME }}`: Access redacted secrets.
|
|
274
|
+
- `${{ env.NAME }}`: Access environment variables.
|
|
275
|
+
|
|
276
|
+
Standard JavaScript-like expressions are supported: `${{ steps.build.status == 'success' ? 'š' : 'ā' }}`.
|
|
277
|
+
|
|
278
|
+
---
|
|
279
|
+
|
|
263
280
|
outputs:
|
|
264
281
|
slack_message: ${{ steps.notify.output }}
|
|
265
282
|
```
|
|
@@ -274,8 +291,11 @@ Keystone supports several specialized step types:
|
|
|
274
291
|
- `llm`: Prompt an agent and get structured or unstructured responses. Supports `schema` (JSON Schema) for structured output.
|
|
275
292
|
- `allowClarification`: Boolean (default `false`). If `true`, allows the LLM to ask clarifying questions back to the user or suspend the workflow if no human is available.
|
|
276
293
|
- `maxIterations`: Number (default `10`). Maximum number of tool-calling loops allowed for the agent.
|
|
294
|
+
- `allowInsecure`: Boolean (default `false`). Set `true` to allow risky tool execution.
|
|
295
|
+
- `allowOutsideCwd`: Boolean (default `false`). Set `true` to allow tools to access files outside of the current working directory.
|
|
277
296
|
- `request`: Make HTTP requests (GET, POST, etc.).
|
|
278
297
|
- `file`: Read, write, or append to files.
|
|
298
|
+
- `allowOutsideCwd`: Boolean (default `false`). Set `true` to allow reading/writing files outside of the current working directory.
|
|
279
299
|
- `human`: Pause execution for manual confirmation or text input.
|
|
280
300
|
- `inputType: confirm`: Simple Enter-to-continue prompt.
|
|
281
301
|
- `inputType: text`: Prompt for a string input, available via `${{ steps.id.output }}`.
|
|
@@ -352,6 +372,8 @@ You are a technical communications expert. Your goal is to take technical output
|
|
|
352
372
|
|
|
353
373
|
Agents can be equipped with tools, which are essentially workflow steps they can choose to execute. You can define tools in the agent definition, or directly in an LLM step within a workflow.
|
|
354
374
|
|
|
375
|
+
Tool arguments are passed to the tool's execution step via the `args` variable.
|
|
376
|
+
|
|
355
377
|
**`.keystone/workflows/agents/developer.md`**
|
|
356
378
|
```markdown
|
|
357
379
|
---
|
|
@@ -363,6 +385,18 @@ tools:
|
|
|
363
385
|
id: list-files-tool
|
|
364
386
|
type: shell
|
|
365
387
|
run: ls -F
|
|
388
|
+
- name: read_file
|
|
389
|
+
description: Read a specific file
|
|
390
|
+
parameters:
|
|
391
|
+
type: object
|
|
392
|
+
properties:
|
|
393
|
+
path: { type: string }
|
|
394
|
+
required: [path]
|
|
395
|
+
execution:
|
|
396
|
+
id: read-file-tool
|
|
397
|
+
type: file
|
|
398
|
+
op: read
|
|
399
|
+
path: ${{ args.path }}
|
|
366
400
|
---
|
|
367
401
|
You are a software developer. You can use tools to explore the codebase.
|
|
368
402
|
```
|
package/package.json
CHANGED
package/src/cli.ts
CHANGED
|
@@ -9,7 +9,7 @@ import architectAgent from './templates/agents/keystone-architect.md' with { typ
|
|
|
9
9
|
// Default templates
|
|
10
10
|
import scaffoldWorkflow from './templates/scaffold-feature.yaml' with { type: 'text' };
|
|
11
11
|
|
|
12
|
-
import { WorkflowDb } from './db/workflow-db.ts';
|
|
12
|
+
import { WorkflowDb, type WorkflowRun } from './db/workflow-db.ts';
|
|
13
13
|
import { WorkflowParser } from './parser/workflow-parser.ts';
|
|
14
14
|
import { ConfigLoader } from './utils/config-loader.ts';
|
|
15
15
|
import { ConsoleLogger } from './utils/logger.ts';
|
|
@@ -279,7 +279,79 @@ program
|
|
|
279
279
|
}
|
|
280
280
|
});
|
|
281
281
|
|
|
282
|
-
//
|
|
282
|
+
// ===== keystone workflows =====
|
|
283
|
+
program
|
|
284
|
+
.command('workflows')
|
|
285
|
+
.description('List available workflows')
|
|
286
|
+
.action(() => {
|
|
287
|
+
const workflows = WorkflowRegistry.listWorkflows();
|
|
288
|
+
if (workflows.length === 0) {
|
|
289
|
+
console.log('No workflows found. Run "keystone init" to seed default workflows.');
|
|
290
|
+
return;
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
console.log('\nšļø Available Workflows:');
|
|
294
|
+
for (const w of workflows) {
|
|
295
|
+
console.log(`\n ${w.name}`);
|
|
296
|
+
if (w.description) {
|
|
297
|
+
console.log(` ${w.description}`);
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
console.log('');
|
|
301
|
+
});
|
|
302
|
+
|
|
303
|
+
// ===== keystone optimize =====
|
|
304
|
+
program
|
|
305
|
+
.command('optimize')
|
|
306
|
+
.description('Optimize a specific step in a workflow using iterative evaluation')
|
|
307
|
+
.argument('<workflow>', 'Workflow name or path to workflow file')
|
|
308
|
+
.requiredOption('-t, --target <step_id>', 'Target step ID to optimize')
|
|
309
|
+
.option('-n, --iterations <number>', 'Number of optimization iterations', '5')
|
|
310
|
+
.option('-i, --input <key=value...>', 'Input values for evaluation')
|
|
311
|
+
.action(async (workflowPath, options) => {
|
|
312
|
+
try {
|
|
313
|
+
const { OptimizationRunner } = await import('./runner/optimization-runner.ts');
|
|
314
|
+
const resolvedPath = WorkflowRegistry.resolvePath(workflowPath);
|
|
315
|
+
const workflow = WorkflowParser.loadWorkflow(resolvedPath);
|
|
316
|
+
|
|
317
|
+
// Parse inputs
|
|
318
|
+
const inputs: Record<string, unknown> = {};
|
|
319
|
+
if (options.input) {
|
|
320
|
+
for (const pair of options.input) {
|
|
321
|
+
const index = pair.indexOf('=');
|
|
322
|
+
if (index > 0) {
|
|
323
|
+
const key = pair.slice(0, index);
|
|
324
|
+
const value = pair.slice(index + 1);
|
|
325
|
+
try {
|
|
326
|
+
inputs[key] = JSON.parse(value);
|
|
327
|
+
} catch {
|
|
328
|
+
inputs[key] = value;
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
const runner = new OptimizationRunner(workflow, {
|
|
335
|
+
workflowPath: resolvedPath,
|
|
336
|
+
targetStepId: options.target,
|
|
337
|
+
iterations: Number.parseInt(options.iterations, 10),
|
|
338
|
+
inputs,
|
|
339
|
+
});
|
|
340
|
+
|
|
341
|
+
console.log('šļø Keystone Prompt Optimization');
|
|
342
|
+
const { bestPrompt, bestScore } = await runner.optimize();
|
|
343
|
+
|
|
344
|
+
console.log('\n⨠Optimization Complete!');
|
|
345
|
+
console.log(`š Best Score: ${bestScore}/100`);
|
|
346
|
+
console.log('\nBest Prompt/Command:');
|
|
347
|
+
console.log(''.padEnd(80, '-'));
|
|
348
|
+
console.log(bestPrompt);
|
|
349
|
+
console.log(''.padEnd(80, '-'));
|
|
350
|
+
} catch (error) {
|
|
351
|
+
console.error('ā Optimization failed:', error instanceof Error ? error.message : error);
|
|
352
|
+
process.exit(1);
|
|
353
|
+
}
|
|
354
|
+
});
|
|
283
355
|
|
|
284
356
|
// ===== keystone resume =====
|
|
285
357
|
program
|
|
@@ -347,40 +419,180 @@ program
|
|
|
347
419
|
}
|
|
348
420
|
});
|
|
349
421
|
|
|
350
|
-
//
|
|
351
|
-
|
|
352
|
-
// ===== keystone maintenance =====
|
|
422
|
+
// ===== keystone history =====
|
|
353
423
|
program
|
|
354
|
-
.command('
|
|
355
|
-
.description('
|
|
356
|
-
.option('--
|
|
424
|
+
.command('history')
|
|
425
|
+
.description('Show recent workflow runs')
|
|
426
|
+
.option('-l, --limit <number>', 'Limit the number of runs to show', '50')
|
|
357
427
|
.action(async (options) => {
|
|
358
428
|
try {
|
|
359
|
-
const
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
429
|
+
const db = new WorkflowDb();
|
|
430
|
+
const limit = Number.parseInt(options.limit, 10);
|
|
431
|
+
const runs = await db.listRuns(limit);
|
|
432
|
+
db.close();
|
|
433
|
+
|
|
434
|
+
if (runs.length === 0) {
|
|
435
|
+
console.log('No workflow runs found.');
|
|
436
|
+
return;
|
|
363
437
|
}
|
|
364
438
|
|
|
365
|
-
console.log('
|
|
439
|
+
console.log('\nšļø Workflow Run History:');
|
|
440
|
+
console.log(''.padEnd(100, '-'));
|
|
441
|
+
console.log(
|
|
442
|
+
`${'ID'.padEnd(10)} ${'Workflow'.padEnd(25)} ${'Status'.padEnd(15)} ${'Started At'}`
|
|
443
|
+
);
|
|
444
|
+
console.log(''.padEnd(100, '-'));
|
|
445
|
+
|
|
446
|
+
for (const run of runs) {
|
|
447
|
+
const id = run.id.slice(0, 8);
|
|
448
|
+
const status = run.status;
|
|
449
|
+
const color =
|
|
450
|
+
status === 'success' ? '\x1b[32m' : status === 'failed' ? '\x1b[31m' : '\x1b[33m';
|
|
451
|
+
const reset = '\x1b[0m';
|
|
452
|
+
|
|
453
|
+
console.log(
|
|
454
|
+
`${id.padEnd(10)} ${run.workflow_name.padEnd(25)} ${color}${status.padEnd(
|
|
455
|
+
15
|
|
456
|
+
)}${reset} ${new Date(run.started_at).toLocaleString()}`
|
|
457
|
+
);
|
|
458
|
+
}
|
|
459
|
+
console.log('');
|
|
460
|
+
} catch (error) {
|
|
461
|
+
console.error('ā Failed to list runs:', error instanceof Error ? error.message : error);
|
|
462
|
+
process.exit(1);
|
|
463
|
+
}
|
|
464
|
+
});
|
|
465
|
+
|
|
466
|
+
// ===== keystone logs =====
|
|
467
|
+
program
|
|
468
|
+
.command('logs')
|
|
469
|
+
.description('Show logs for a specific workflow run')
|
|
470
|
+
.argument('<run_id>', 'Run ID to show logs for')
|
|
471
|
+
.option('-v, --verbose', 'Show detailed step outputs')
|
|
472
|
+
.action(async (runId, options) => {
|
|
473
|
+
try {
|
|
366
474
|
const db = new WorkflowDb();
|
|
475
|
+
const run = await db.getRun(runId);
|
|
367
476
|
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
477
|
+
if (!run) {
|
|
478
|
+
// Try searching by short ID
|
|
479
|
+
const allRuns = await db.listRuns(200);
|
|
480
|
+
const matching = allRuns.find((r) => r.id.startsWith(runId));
|
|
481
|
+
if (matching) {
|
|
482
|
+
const detailedRun = await db.getRun(matching.id);
|
|
483
|
+
if (detailedRun) {
|
|
484
|
+
await showRunLogs(detailedRun, db, !!options.verbose);
|
|
485
|
+
db.close();
|
|
486
|
+
return;
|
|
487
|
+
}
|
|
488
|
+
}
|
|
371
489
|
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
490
|
+
console.error(`ā Run not found: ${runId}`);
|
|
491
|
+
db.close();
|
|
492
|
+
process.exit(1);
|
|
493
|
+
}
|
|
375
494
|
|
|
495
|
+
await showRunLogs(run, db, !!options.verbose);
|
|
376
496
|
db.close();
|
|
377
|
-
console.log('\n⨠Maintenance completed successfully!');
|
|
378
497
|
} catch (error) {
|
|
379
|
-
console.error('ā
|
|
498
|
+
console.error('ā Failed to show logs:', error instanceof Error ? error.message : error);
|
|
380
499
|
process.exit(1);
|
|
381
500
|
}
|
|
382
501
|
});
|
|
383
502
|
|
|
503
|
+
async function showRunLogs(run: WorkflowRun, db: WorkflowDb, verbose: boolean) {
|
|
504
|
+
console.log(`\nšļø Run: ${run.workflow_name} (${run.id})`);
|
|
505
|
+
console.log(` Status: ${run.status}`);
|
|
506
|
+
console.log(` Started: ${new Date(run.started_at).toLocaleString()}`);
|
|
507
|
+
if (run.completed_at) {
|
|
508
|
+
console.log(` Completed: ${new Date(run.completed_at).toLocaleString()}`);
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
const steps = await db.getStepsByRun(run.id);
|
|
512
|
+
console.log(`\nSteps (${steps.length}):`);
|
|
513
|
+
console.log(''.padEnd(100, '-'));
|
|
514
|
+
|
|
515
|
+
for (const step of steps) {
|
|
516
|
+
const statusColor =
|
|
517
|
+
step.status === 'success' ? '\x1b[32m' : step.status === 'failed' ? '\x1b[31m' : '\x1b[33m';
|
|
518
|
+
const reset = '\x1b[0m';
|
|
519
|
+
|
|
520
|
+
let label = step.step_id;
|
|
521
|
+
if (step.iteration_index !== null) {
|
|
522
|
+
label += ` [${step.iteration_index}]`;
|
|
523
|
+
}
|
|
524
|
+
|
|
525
|
+
console.log(`${statusColor}${step.status.toUpperCase().padEnd(10)}${reset} ${label}`);
|
|
526
|
+
|
|
527
|
+
if (step.error) {
|
|
528
|
+
console.log(` \x1b[31mError: ${step.error}\x1b[0m`);
|
|
529
|
+
}
|
|
530
|
+
|
|
531
|
+
if (verbose && step.output) {
|
|
532
|
+
try {
|
|
533
|
+
const output = JSON.parse(step.output);
|
|
534
|
+
console.log(
|
|
535
|
+
` Output: ${JSON.stringify(output, null, 2).replace(/\n/g, '\n ')}`
|
|
536
|
+
);
|
|
537
|
+
} catch {
|
|
538
|
+
console.log(` Output: ${step.output}`);
|
|
539
|
+
}
|
|
540
|
+
}
|
|
541
|
+
}
|
|
542
|
+
|
|
543
|
+
if (run.outputs) {
|
|
544
|
+
console.log('\nFinal Outputs:');
|
|
545
|
+
try {
|
|
546
|
+
const parsed = JSON.parse(run.outputs);
|
|
547
|
+
console.log(JSON.stringify(parsed, null, 2));
|
|
548
|
+
} catch {
|
|
549
|
+
console.log(run.outputs);
|
|
550
|
+
}
|
|
551
|
+
}
|
|
552
|
+
|
|
553
|
+
if (run.error) {
|
|
554
|
+
console.log(`\n\x1b[31mWorkflow Error:\x1b[0m ${run.error}`);
|
|
555
|
+
}
|
|
556
|
+
}
|
|
557
|
+
|
|
558
|
+
// ===== keystone prune / maintenance =====
|
|
559
|
+
async function performMaintenance(days: number) {
|
|
560
|
+
try {
|
|
561
|
+
console.log(`š§¹ Starting maintenance (pruning runs older than ${days} days)...`);
|
|
562
|
+
const db = new WorkflowDb();
|
|
563
|
+
const count = await db.pruneRuns(days);
|
|
564
|
+
console.log(` ā Pruned ${count} old run(s)`);
|
|
565
|
+
|
|
566
|
+
console.log(' Vacuuming database (reclaiming space)...');
|
|
567
|
+
await db.vacuum();
|
|
568
|
+
console.log(' ā Vacuum complete');
|
|
569
|
+
|
|
570
|
+
db.close();
|
|
571
|
+
console.log('\n⨠Maintenance completed successfully!');
|
|
572
|
+
} catch (error) {
|
|
573
|
+
console.error('ā Maintenance failed:', error instanceof Error ? error.message : error);
|
|
574
|
+
process.exit(1);
|
|
575
|
+
}
|
|
576
|
+
}
|
|
577
|
+
|
|
578
|
+
program
|
|
579
|
+
.command('prune')
|
|
580
|
+
.description('Delete old workflow runs from the database (alias for maintenance)')
|
|
581
|
+
.option('--days <number>', 'Days to keep', '30')
|
|
582
|
+
.action(async (options) => {
|
|
583
|
+
const days = Number.parseInt(options.days, 10);
|
|
584
|
+
await performMaintenance(days);
|
|
585
|
+
});
|
|
586
|
+
|
|
587
|
+
program
|
|
588
|
+
.command('maintenance')
|
|
589
|
+
.description('Perform database maintenance (prune old runs and vacuum)')
|
|
590
|
+
.option('--days <days>', 'Delete runs older than this many days', '30')
|
|
591
|
+
.action(async (options) => {
|
|
592
|
+
const days = Number.parseInt(options.days, 10);
|
|
593
|
+
await performMaintenance(days);
|
|
594
|
+
});
|
|
595
|
+
|
|
384
596
|
// ===== keystone ui =====
|
|
385
597
|
program
|
|
386
598
|
.command('ui')
|
package/src/db/memory-db.ts
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import type { Database } from 'bun:sqlite';
|
|
2
2
|
import { randomUUID } from 'node:crypto';
|
|
3
|
+
import { existsSync, mkdirSync } from 'node:fs';
|
|
4
|
+
import { dirname } from 'node:path';
|
|
3
5
|
import * as sqliteVec from 'sqlite-vec';
|
|
4
6
|
import './sqlite-setup.ts';
|
|
5
7
|
|
|
@@ -22,6 +24,10 @@ export class MemoryDb {
|
|
|
22
24
|
this.db = cached.db;
|
|
23
25
|
} else {
|
|
24
26
|
const { Database } = require('bun:sqlite');
|
|
27
|
+
const dir = dirname(dbPath);
|
|
28
|
+
if (!existsSync(dir)) {
|
|
29
|
+
mkdirSync(dir, { recursive: true });
|
|
30
|
+
}
|
|
25
31
|
this.db = new Database(dbPath, { create: true });
|
|
26
32
|
|
|
27
33
|
// Load sqlite-vec extension
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { afterEach, describe, expect, it, mock, spyOn } from 'bun:test';
|
|
2
|
+
import type { Logger } from '../utils/logger';
|
|
3
|
+
import { setupSqlite } from './sqlite-setup';
|
|
4
|
+
|
|
5
|
+
describe('setupSqlite', () => {
|
|
6
|
+
const originalPlatform = process.platform;
|
|
7
|
+
|
|
8
|
+
afterEach(() => {
|
|
9
|
+
Object.defineProperty(process, 'platform', {
|
|
10
|
+
value: originalPlatform,
|
|
11
|
+
});
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
it('does nothing on non-darwin platforms', () => {
|
|
15
|
+
Object.defineProperty(process, 'platform', { value: 'linux' });
|
|
16
|
+
const logger: Logger = {
|
|
17
|
+
log: mock(() => {}),
|
|
18
|
+
warn: mock(() => {}),
|
|
19
|
+
error: mock(() => {}),
|
|
20
|
+
info: mock(() => {}),
|
|
21
|
+
};
|
|
22
|
+
setupSqlite(logger);
|
|
23
|
+
expect(logger.log).not.toHaveBeenCalled();
|
|
24
|
+
expect(logger.warn).not.toHaveBeenCalled();
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
it('logs warning if no custom sqlite found on darwin', () => {
|
|
28
|
+
Object.defineProperty(process, 'platform', { value: 'darwin' });
|
|
29
|
+
const logger: Logger = {
|
|
30
|
+
log: mock(() => {}),
|
|
31
|
+
warn: mock(() => {}),
|
|
32
|
+
error: mock(() => {}),
|
|
33
|
+
info: mock(() => {}),
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
// Mock Bun.spawnSync for brew
|
|
37
|
+
const spawnSpy = spyOn(Bun, 'spawnSync').mockImplementation(
|
|
38
|
+
() => ({ success: false }) as unknown as ReturnType<typeof Bun.spawnSync>
|
|
39
|
+
);
|
|
40
|
+
|
|
41
|
+
try {
|
|
42
|
+
setupSqlite(logger);
|
|
43
|
+
} finally {
|
|
44
|
+
spawnSpy.mockRestore();
|
|
45
|
+
}
|
|
46
|
+
});
|
|
47
|
+
});
|
package/src/db/workflow-db.ts
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import { Database } from 'bun:sqlite';
|
|
2
|
+
import { existsSync, mkdirSync } from 'node:fs';
|
|
3
|
+
import { dirname } from 'node:path';
|
|
2
4
|
import './sqlite-setup.ts';
|
|
3
5
|
import {
|
|
4
6
|
StepStatus as StepStatusConst,
|
|
@@ -40,6 +42,10 @@ export class WorkflowDb {
|
|
|
40
42
|
private db: Database;
|
|
41
43
|
|
|
42
44
|
constructor(public readonly dbPath = '.keystone/state.db') {
|
|
45
|
+
const dir = dirname(dbPath);
|
|
46
|
+
if (!existsSync(dir)) {
|
|
47
|
+
mkdirSync(dir, { recursive: true });
|
|
48
|
+
}
|
|
43
49
|
this.db = new Database(dbPath, { create: true });
|
|
44
50
|
this.db.exec('PRAGMA journal_mode = WAL;'); // Write-ahead logging
|
|
45
51
|
this.db.exec('PRAGMA foreign_keys = ON;'); // Enable foreign key enforcement
|
|
@@ -29,6 +29,7 @@ export interface ExpressionContext {
|
|
|
29
29
|
secrets?: Record<string, string>;
|
|
30
30
|
steps?: Record<string, { output?: unknown; outputs?: Record<string, unknown>; status?: string }>;
|
|
31
31
|
item?: unknown;
|
|
32
|
+
args?: unknown;
|
|
32
33
|
index?: number;
|
|
33
34
|
env?: Record<string, string>;
|
|
34
35
|
output?: unknown;
|
|
@@ -295,6 +296,7 @@ export class ExpressionEvaluator {
|
|
|
295
296
|
secrets: context.secrets || {},
|
|
296
297
|
steps: context.steps || {},
|
|
297
298
|
item: context.item,
|
|
299
|
+
args: context.args,
|
|
298
300
|
index: context.index,
|
|
299
301
|
env: context.env || {},
|
|
300
302
|
stdout: contextAsRecord.stdout, // For transform expressions
|
package/src/parser/schema.ts
CHANGED
|
@@ -95,6 +95,9 @@ const LlmStepSchema = BaseStepSchema.extend({
|
|
|
95
95
|
])
|
|
96
96
|
)
|
|
97
97
|
.optional(),
|
|
98
|
+
useStandardTools: z.boolean().optional(),
|
|
99
|
+
allowOutsideCwd: z.boolean().optional(),
|
|
100
|
+
allowInsecure: z.boolean().optional(),
|
|
98
101
|
});
|
|
99
102
|
|
|
100
103
|
const WorkflowStepSchema = BaseStepSchema.extend({
|