keystone-cli 0.1.1 → 0.2.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 +52 -15
- package/package.json +1 -1
- package/src/cli.ts +90 -81
- package/src/db/workflow-db.ts +0 -7
- package/src/expression/evaluator.test.ts +42 -0
- package/src/expression/evaluator.ts +28 -0
- package/src/parser/agent-parser.test.ts +10 -0
- package/src/parser/agent-parser.ts +2 -1
- package/src/parser/config-schema.ts +13 -5
- package/src/parser/workflow-parser.ts +0 -5
- package/src/runner/llm-adapter.test.ts +0 -8
- package/src/runner/llm-adapter.ts +33 -10
- package/src/runner/llm-executor.test.ts +59 -18
- package/src/runner/llm-executor.ts +1 -1
- package/src/runner/mcp-client.test.ts +166 -88
- package/src/runner/mcp-client.ts +156 -22
- package/src/runner/mcp-manager.test.ts +73 -15
- package/src/runner/mcp-manager.ts +44 -18
- package/src/runner/mcp-server.test.ts +4 -1
- package/src/runner/mcp-server.ts +25 -11
- package/src/runner/shell-executor.ts +3 -3
- package/src/runner/step-executor.ts +10 -9
- package/src/runner/tool-integration.test.ts +21 -14
- package/src/runner/workflow-runner.ts +25 -5
- package/src/templates/agents/explore.md +54 -0
- package/src/templates/agents/general.md +8 -0
- package/src/templates/agents/keystone-architect.md +54 -0
- package/src/templates/agents/my-agent.md +3 -0
- package/src/templates/agents/summarizer.md +28 -0
- package/src/templates/agents/test-agent.md +10 -0
- package/src/templates/approval-process.yaml +36 -0
- package/src/templates/basic-inputs.yaml +19 -0
- package/src/templates/basic-shell.yaml +20 -0
- package/src/templates/batch-processor.yaml +43 -0
- package/src/templates/cleanup-finally.yaml +22 -0
- package/src/templates/composition-child.yaml +13 -0
- package/src/templates/composition-parent.yaml +14 -0
- package/src/templates/data-pipeline.yaml +38 -0
- package/src/templates/full-feature-demo.yaml +64 -0
- package/src/templates/human-interaction.yaml +12 -0
- package/src/templates/invalid.yaml +5 -0
- package/src/templates/llm-agent.yaml +8 -0
- package/src/templates/loop-parallel.yaml +37 -0
- package/src/templates/retry-policy.yaml +36 -0
- package/src/templates/scaffold-feature.yaml +48 -0
- package/src/templates/state.db +0 -0
- package/src/templates/state.db-shm +0 -0
- package/src/templates/state.db-wal +0 -0
- package/src/templates/stop-watch.yaml +17 -0
- package/src/templates/workflow.db +0 -0
- package/src/utils/config-loader.test.ts +2 -2
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: keystone-architect
|
|
3
|
+
description: "Expert at designing Keystone workflows and agents"
|
|
4
|
+
model: gpt-4o
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# Role
|
|
8
|
+
You are the Keystone Architect. Your goal is to design and generate high-quality Keystone workflows (.yaml) and agents (.md). You understand the underlying schema and expression syntax perfectly.
|
|
9
|
+
|
|
10
|
+
# Knowledge Base
|
|
11
|
+
|
|
12
|
+
## Workflow Schema (.yaml)
|
|
13
|
+
- **name**: Unique identifier for the workflow.
|
|
14
|
+
- **inputs**: Map of `{ type: string, default: any, description: string }` under the `inputs` key.
|
|
15
|
+
- **outputs**: Map of expressions (e.g., `${{ steps.id.output }}`) under the `outputs` key.
|
|
16
|
+
- **steps**: Array of step objects. Each step MUST have an `id` and a `type`:
|
|
17
|
+
- **shell**: `{ id, type: 'shell', run, dir, env, transform }`
|
|
18
|
+
- **llm**: `{ id, type: 'llm', agent, prompt, schema }`
|
|
19
|
+
- **workflow**: `{ id, type: 'workflow', path, inputs }`
|
|
20
|
+
- **file**: `{ id, type: 'file', path, op: 'read'|'write'|'append', content }`
|
|
21
|
+
- **request**: `{ id, type: 'request', url, method, body, headers }`
|
|
22
|
+
- **human**: `{ id, type: 'human', message, inputType: 'confirm'|'text' }`
|
|
23
|
+
- **sleep**: `{ id, type: 'sleep', duration }`
|
|
24
|
+
- **Common Step Fields**: `needs` (array of IDs), `if` (expression), `retry`, `foreach`, `concurrency`.
|
|
25
|
+
- **IMPORTANT**: Steps run in **parallel** by default. To ensure sequential execution, a step must explicitly list the previous step's ID in its `needs` array.
|
|
26
|
+
|
|
27
|
+
## Agent Schema (.md)
|
|
28
|
+
Markdown files with YAML frontmatter:
|
|
29
|
+
- **name**: Agent name.
|
|
30
|
+
- **model**: (Optional) e.g., `gpt-4o`, `claude-sonnet-4.5`.
|
|
31
|
+
- **tools**: Array of `{ name, parameters, execution }` where `execution` is a standard Step object.
|
|
32
|
+
- **Body**: The Markdown body is the `systemPrompt`.
|
|
33
|
+
|
|
34
|
+
## Expression Syntax
|
|
35
|
+
- `${{ inputs.name }}`
|
|
36
|
+
- `${{ steps.id.output }}`
|
|
37
|
+
- `${{ steps.id.status }}`
|
|
38
|
+
- `${{ args.paramName }}` (used inside agent tools)
|
|
39
|
+
- Standard JS-like expressions: `${{ steps.count > 0 ? 'yes' : 'no' }}`
|
|
40
|
+
|
|
41
|
+
# Output Instructions
|
|
42
|
+
When asked to design a feature:
|
|
43
|
+
1. Provide the necessary Keystone files (Workflows and Agents).
|
|
44
|
+
2. **IMPORTANT**: Return ONLY a raw JSON object. Do not include markdown code blocks, preamble, or postamble.
|
|
45
|
+
|
|
46
|
+
The JSON structure must be:
|
|
47
|
+
{
|
|
48
|
+
"files": [
|
|
49
|
+
{
|
|
50
|
+
"path": "workflows/...",
|
|
51
|
+
"content": "..."
|
|
52
|
+
}
|
|
53
|
+
]
|
|
54
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: summarizer
|
|
3
|
+
description: "Summarizes text content"
|
|
4
|
+
model: gpt-4o
|
|
5
|
+
tools:
|
|
6
|
+
- name: read_file
|
|
7
|
+
description: "Read the contents of a file"
|
|
8
|
+
parameters:
|
|
9
|
+
type: object
|
|
10
|
+
properties:
|
|
11
|
+
filepath:
|
|
12
|
+
type: string
|
|
13
|
+
description: "The path to the file to read"
|
|
14
|
+
required: ["filepath"]
|
|
15
|
+
execution:
|
|
16
|
+
type: file
|
|
17
|
+
op: read
|
|
18
|
+
path: "${{ args.filepath }}"
|
|
19
|
+
---
|
|
20
|
+
|
|
21
|
+
# Identity
|
|
22
|
+
You are a concise summarizer. Your goal is to extract the key points from any text and present them in a clear, brief format.
|
|
23
|
+
|
|
24
|
+
## Guidelines
|
|
25
|
+
- Focus on the main ideas and key takeaways
|
|
26
|
+
- Keep summaries under 3-5 sentences unless more detail is explicitly requested
|
|
27
|
+
- Use clear, simple language
|
|
28
|
+
- Maintain objectivity and accuracy
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
name: approval-process
|
|
2
|
+
description: "A workflow demonstrating human-in-the-loop and conditional logic"
|
|
3
|
+
|
|
4
|
+
inputs:
|
|
5
|
+
request_details: { type: string, default: "Access to production database" }
|
|
6
|
+
|
|
7
|
+
steps:
|
|
8
|
+
- id: request_approval
|
|
9
|
+
type: human
|
|
10
|
+
message: "Do you approve the following request: ${{ inputs.request_details }}?"
|
|
11
|
+
inputType: confirm
|
|
12
|
+
|
|
13
|
+
- id: log_approval
|
|
14
|
+
type: shell
|
|
15
|
+
if: ${{ steps.request_approval.output == true }}
|
|
16
|
+
run: echo "Request approved. Proceeding with implementation."
|
|
17
|
+
needs: [request_approval]
|
|
18
|
+
|
|
19
|
+
- id: log_rejection
|
|
20
|
+
type: shell
|
|
21
|
+
if: ${{ steps.request_approval.output == false }}
|
|
22
|
+
run: echo "Request rejected. Notifying requester."
|
|
23
|
+
needs: [request_approval]
|
|
24
|
+
|
|
25
|
+
- id: get_rejection_reason
|
|
26
|
+
type: human
|
|
27
|
+
if: ${{ steps.request_approval.output == false }}
|
|
28
|
+
message: "Please provide a reason for rejection:"
|
|
29
|
+
inputType: text
|
|
30
|
+
needs: [request_approval]
|
|
31
|
+
|
|
32
|
+
- id: finalize_rejection
|
|
33
|
+
type: shell
|
|
34
|
+
if: ${{ steps.request_approval.output == false }}
|
|
35
|
+
run: echo "Rejection reason - ${{ steps.get_rejection_reason.output }}"
|
|
36
|
+
needs: [get_rejection_reason]
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
name: basic-inputs
|
|
2
|
+
description: "A simple workflow that greets a user with optional repetition"
|
|
3
|
+
inputs:
|
|
4
|
+
user_name:
|
|
5
|
+
type: string
|
|
6
|
+
description: "The name of the person to greet"
|
|
7
|
+
default: "World"
|
|
8
|
+
count:
|
|
9
|
+
type: number
|
|
10
|
+
description: "Number of times to greet"
|
|
11
|
+
default: 1
|
|
12
|
+
|
|
13
|
+
steps:
|
|
14
|
+
- id: hello
|
|
15
|
+
type: shell
|
|
16
|
+
run: |
|
|
17
|
+
for i in $(seq 1 ${{ inputs.count }}); do
|
|
18
|
+
echo "Hello, ${{ escape(inputs.user_name) }}! (Attempt $i)"
|
|
19
|
+
done
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
name: basic-shell
|
|
2
|
+
description: "A simple example workflow demonstrating basic features"
|
|
3
|
+
|
|
4
|
+
inputs:
|
|
5
|
+
greeting: { type: string, default: "Hello" }
|
|
6
|
+
name: { type: string, default: "World" }
|
|
7
|
+
|
|
8
|
+
outputs:
|
|
9
|
+
message: ${{ steps.create_message.output }}
|
|
10
|
+
|
|
11
|
+
steps:
|
|
12
|
+
- id: create_message
|
|
13
|
+
type: shell
|
|
14
|
+
run: echo "${{ escape(inputs.greeting) }}, ${{ escape(inputs.name) }}!"
|
|
15
|
+
transform: "stdout.trim()"
|
|
16
|
+
|
|
17
|
+
- id: print_message
|
|
18
|
+
type: shell
|
|
19
|
+
needs: [create_message]
|
|
20
|
+
run: echo "Generated message - ${{ steps.create_message.output }}"
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
name: batch-processor
|
|
2
|
+
description: "Process multiple files in parallel"
|
|
3
|
+
|
|
4
|
+
inputs:
|
|
5
|
+
target_dir: { type: string, default: "./data" }
|
|
6
|
+
|
|
7
|
+
outputs:
|
|
8
|
+
processed_count: ${{ steps.process_files.outputs.length }}
|
|
9
|
+
|
|
10
|
+
env:
|
|
11
|
+
API_KEY: ${{ secrets.API_KEY }}
|
|
12
|
+
|
|
13
|
+
steps:
|
|
14
|
+
# 1. Dynamic Input Generation
|
|
15
|
+
- id: list_files
|
|
16
|
+
type: shell
|
|
17
|
+
run: "ls ${{ inputs.target_dir }}/*.txt"
|
|
18
|
+
# Extract stdout lines into an array
|
|
19
|
+
transform: "stdout.split('\\n').filter(Boolean)"
|
|
20
|
+
|
|
21
|
+
# 2. Matrix/Looping (The Missing Piece)
|
|
22
|
+
- id: process_files
|
|
23
|
+
type: llm
|
|
24
|
+
needs: [list_files]
|
|
25
|
+
# Iterates over the array from the previous step
|
|
26
|
+
foreach: ${{ steps.list_files.output }}
|
|
27
|
+
concurrency: 5
|
|
28
|
+
# Robustness (The Missing Piece)
|
|
29
|
+
retry:
|
|
30
|
+
count: 3
|
|
31
|
+
backoff: "exponential"
|
|
32
|
+
timeout: 30000 # 30s limit per item
|
|
33
|
+
# Step Definition
|
|
34
|
+
agent: summarizer
|
|
35
|
+
prompt: "Summarize this file: ${{ item }}"
|
|
36
|
+
|
|
37
|
+
# 3. Conditional Logic
|
|
38
|
+
- id: notify
|
|
39
|
+
type: request
|
|
40
|
+
needs: [process_files]
|
|
41
|
+
if: ${{ steps.process_files.items.every(s => s.status == 'success') }}
|
|
42
|
+
method: POST
|
|
43
|
+
url: "https://webhook.site/..."
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
name: cleanup-finally
|
|
2
|
+
description: "Test the finally block"
|
|
3
|
+
|
|
4
|
+
steps:
|
|
5
|
+
- id: step1
|
|
6
|
+
type: shell
|
|
7
|
+
run: echo "Main step 1"
|
|
8
|
+
|
|
9
|
+
- id: fail_step
|
|
10
|
+
type: shell
|
|
11
|
+
run: "exit 1"
|
|
12
|
+
if: ${{ inputs.should_fail }}
|
|
13
|
+
|
|
14
|
+
finally:
|
|
15
|
+
- id: cleanup
|
|
16
|
+
type: shell
|
|
17
|
+
run: echo "Cleanup task executed"
|
|
18
|
+
|
|
19
|
+
- id: cleanup_with_dep
|
|
20
|
+
type: shell
|
|
21
|
+
needs: [cleanup]
|
|
22
|
+
run: echo "Cleanup with dependency executed"
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
name: composition-child
|
|
2
|
+
description: "A simple child workflow"
|
|
3
|
+
|
|
4
|
+
inputs:
|
|
5
|
+
name: { type: string }
|
|
6
|
+
|
|
7
|
+
outputs:
|
|
8
|
+
result: "Hello from child, ${{ inputs.name }}!"
|
|
9
|
+
|
|
10
|
+
steps:
|
|
11
|
+
- id: echo_step
|
|
12
|
+
type: shell
|
|
13
|
+
run: echo "Processing ${{ inputs.name }}"
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
name: composition-parent
|
|
2
|
+
description: "A parent workflow that calls a child workflow"
|
|
3
|
+
|
|
4
|
+
steps:
|
|
5
|
+
- id: run_child
|
|
6
|
+
type: workflow
|
|
7
|
+
path: "workflows/composition-child.yaml"
|
|
8
|
+
inputs:
|
|
9
|
+
name: "Keystone User"
|
|
10
|
+
|
|
11
|
+
- id: print_result
|
|
12
|
+
type: shell
|
|
13
|
+
needs: [run_child]
|
|
14
|
+
run: echo "Child workflow result - ${{ steps.run_child.outputs.result }}"
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
name: data-pipeline
|
|
2
|
+
description: "A workflow that demonstrates file operations and cleanup with 'finally'"
|
|
3
|
+
|
|
4
|
+
inputs:
|
|
5
|
+
input_file: { type: string, default: "input.txt" }
|
|
6
|
+
output_file: { type: string, default: "output.txt" }
|
|
7
|
+
|
|
8
|
+
steps:
|
|
9
|
+
- id: prepare_data
|
|
10
|
+
type: file
|
|
11
|
+
op: write
|
|
12
|
+
path: ${{ inputs.input_file }}
|
|
13
|
+
content: "line 1\nline 2\nline 3"
|
|
14
|
+
|
|
15
|
+
- id: read_data
|
|
16
|
+
type: file
|
|
17
|
+
op: read
|
|
18
|
+
path: ${{ inputs.input_file }}
|
|
19
|
+
needs: [prepare_data]
|
|
20
|
+
|
|
21
|
+
- id: process_data
|
|
22
|
+
type: shell
|
|
23
|
+
run: |
|
|
24
|
+
echo "${{ steps.read_data.output }}" | tr '[:lower:]' '[:upper:]'
|
|
25
|
+
transform: "stdout.trim()"
|
|
26
|
+
needs: [read_data]
|
|
27
|
+
|
|
28
|
+
- id: save_result
|
|
29
|
+
type: file
|
|
30
|
+
op: write
|
|
31
|
+
path: ${{ inputs.output_file }}
|
|
32
|
+
content: ${{ steps.process_data.output }}
|
|
33
|
+
needs: [process_data]
|
|
34
|
+
|
|
35
|
+
finally:
|
|
36
|
+
- id: cleanup
|
|
37
|
+
type: shell
|
|
38
|
+
run: rm ${{ inputs.input_file }}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
name: full-feature-demo
|
|
2
|
+
description: "A comprehensive workflow demonstrating multiple feature types"
|
|
3
|
+
|
|
4
|
+
inputs:
|
|
5
|
+
message:
|
|
6
|
+
type: string
|
|
7
|
+
default: "Hello from Keystone!"
|
|
8
|
+
|
|
9
|
+
outputs:
|
|
10
|
+
greeting: ${{ steps.greet.output }}
|
|
11
|
+
file_count: ${{ steps.count_files.output }}
|
|
12
|
+
timestamp: ${{ steps.get_date.output }}
|
|
13
|
+
|
|
14
|
+
steps:
|
|
15
|
+
# Test shell execution
|
|
16
|
+
- id: greet
|
|
17
|
+
type: shell
|
|
18
|
+
run: echo "${{ inputs.message }}"
|
|
19
|
+
|
|
20
|
+
# Test shell with transform
|
|
21
|
+
- id: get_date
|
|
22
|
+
type: shell
|
|
23
|
+
run: date +%s
|
|
24
|
+
transform: parseInt(stdout.trim())
|
|
25
|
+
|
|
26
|
+
# Test file write
|
|
27
|
+
- id: write_file
|
|
28
|
+
type: file
|
|
29
|
+
op: write
|
|
30
|
+
path: /tmp/keystone-test.txt
|
|
31
|
+
content: "Test file created at ${{ steps.get_date.output }}"
|
|
32
|
+
|
|
33
|
+
# Test file read
|
|
34
|
+
- id: read_file
|
|
35
|
+
type: file
|
|
36
|
+
op: read
|
|
37
|
+
path: /tmp/keystone-test.txt
|
|
38
|
+
needs: [write_file]
|
|
39
|
+
|
|
40
|
+
# Test shell with dependencies
|
|
41
|
+
- id: count_files
|
|
42
|
+
type: shell
|
|
43
|
+
needs: [write_file]
|
|
44
|
+
run: ls /tmp/keystone-*.txt | wc -l
|
|
45
|
+
transform: parseInt(stdout.trim())
|
|
46
|
+
|
|
47
|
+
# Test conditional execution
|
|
48
|
+
- id: success_message
|
|
49
|
+
type: shell
|
|
50
|
+
if: ${{ steps.count_files.output > 0 }}
|
|
51
|
+
needs: [count_files]
|
|
52
|
+
run: echo "Found files!"
|
|
53
|
+
|
|
54
|
+
# Test HTTP request
|
|
55
|
+
- id: api_test
|
|
56
|
+
type: request
|
|
57
|
+
url: https://httpbin.org/get
|
|
58
|
+
method: GET
|
|
59
|
+
|
|
60
|
+
# Test sleep
|
|
61
|
+
- id: wait
|
|
62
|
+
type: sleep
|
|
63
|
+
duration: 100
|
|
64
|
+
needs: [api_test]
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
name: human-interaction
|
|
2
|
+
description: A workflow that prompts the user for input and uses it in another step.
|
|
3
|
+
|
|
4
|
+
steps:
|
|
5
|
+
- id: ask_name
|
|
6
|
+
type: human
|
|
7
|
+
message: "What is your name?"
|
|
8
|
+
inputType: text
|
|
9
|
+
|
|
10
|
+
- id: greet
|
|
11
|
+
type: shell
|
|
12
|
+
run: echo "Hello, ${{ escape(steps.ask_name.output) }}!"
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
name: loop-parallel
|
|
2
|
+
description: "Test the foreach race condition fix and .every() support"
|
|
3
|
+
|
|
4
|
+
outputs:
|
|
5
|
+
all_success: ${{ steps.process_items.items.every(s => s.status == 'success') }}
|
|
6
|
+
item_count: ${{ steps.process_items.outputs.length }}
|
|
7
|
+
first_output: ${{ steps.process_items.items[0].output }}
|
|
8
|
+
|
|
9
|
+
steps:
|
|
10
|
+
# Generate test data
|
|
11
|
+
- id: generate_items
|
|
12
|
+
type: shell
|
|
13
|
+
run: "echo 'item1\nitem2\nitem3\nitem4\nitem5'"
|
|
14
|
+
transform: "stdout.split('\\n').filter(Boolean)"
|
|
15
|
+
|
|
16
|
+
# Process items with concurrency (tests race condition fix)
|
|
17
|
+
- id: process_items
|
|
18
|
+
type: shell
|
|
19
|
+
needs: [generate_items]
|
|
20
|
+
foreach: ${{ steps.generate_items.output }}
|
|
21
|
+
concurrency: 3
|
|
22
|
+
run: "echo 'Processing: ${{ item }}' && sleep 0.1"
|
|
23
|
+
transform: "stdout.trim()"
|
|
24
|
+
|
|
25
|
+
# Test conditional using .every() (tests aggregation fix)
|
|
26
|
+
- id: success_notification
|
|
27
|
+
type: shell
|
|
28
|
+
needs: [process_items]
|
|
29
|
+
if: ${{ steps.process_items.items.every(s => s.status == 'success') }}
|
|
30
|
+
run: "echo 'All items processed successfully!'"
|
|
31
|
+
|
|
32
|
+
# Test accessing individual item status
|
|
33
|
+
- id: check_first
|
|
34
|
+
type: shell
|
|
35
|
+
needs: [process_items]
|
|
36
|
+
if: ${{ steps.process_items.items[0].status == 'success' }}
|
|
37
|
+
run: "echo 'First item was successful'"
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
name: retry-policy
|
|
2
|
+
description: "Test retry and timeout features"
|
|
3
|
+
|
|
4
|
+
steps:
|
|
5
|
+
# Test retry with a command that fails first few times
|
|
6
|
+
- id: flaky_command
|
|
7
|
+
type: shell
|
|
8
|
+
run: |
|
|
9
|
+
if [ ! -f /tmp/keystone-retry-test ]; then
|
|
10
|
+
echo "1" > /tmp/keystone-retry-test
|
|
11
|
+
exit 1
|
|
12
|
+
else
|
|
13
|
+
count=$(cat /tmp/keystone-retry-test)
|
|
14
|
+
count=$((count + 1))
|
|
15
|
+
echo $count > /tmp/keystone-retry-test
|
|
16
|
+
if [ $count -lt 3 ]; then
|
|
17
|
+
exit 1
|
|
18
|
+
fi
|
|
19
|
+
echo "Success after $count attempts"
|
|
20
|
+
fi
|
|
21
|
+
retry:
|
|
22
|
+
count: 3
|
|
23
|
+
backoff: exponential
|
|
24
|
+
|
|
25
|
+
# Test timeout (should complete before timeout)
|
|
26
|
+
- id: quick_task
|
|
27
|
+
type: shell
|
|
28
|
+
run: sleep 0.1 && echo "Completed"
|
|
29
|
+
timeout: 5000
|
|
30
|
+
needs: [flaky_command]
|
|
31
|
+
|
|
32
|
+
# Cleanup
|
|
33
|
+
- id: cleanup
|
|
34
|
+
type: shell
|
|
35
|
+
run: rm -f /tmp/keystone-retry-test
|
|
36
|
+
needs: [quick_task]
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
name: scaffold-feature
|
|
2
|
+
description: "Autonomously build new Keystone workflows and agents"
|
|
3
|
+
|
|
4
|
+
steps:
|
|
5
|
+
- id: get_requirements
|
|
6
|
+
type: human
|
|
7
|
+
message: "Describe the workflow you want to build:"
|
|
8
|
+
inputType: text
|
|
9
|
+
|
|
10
|
+
- id: design
|
|
11
|
+
type: llm
|
|
12
|
+
agent: keystone-architect
|
|
13
|
+
needs: [get_requirements]
|
|
14
|
+
prompt: |
|
|
15
|
+
The user wants to build the following:
|
|
16
|
+
<user_requirements>
|
|
17
|
+
${{ steps.get_requirements.output }}
|
|
18
|
+
</user_requirements>
|
|
19
|
+
Generate the necessary Keystone workflow and optionally create an agent where appropriate.
|
|
20
|
+
schema:
|
|
21
|
+
type: object
|
|
22
|
+
properties:
|
|
23
|
+
files:
|
|
24
|
+
type: array
|
|
25
|
+
items:
|
|
26
|
+
type: object
|
|
27
|
+
properties:
|
|
28
|
+
path:
|
|
29
|
+
type: string
|
|
30
|
+
content:
|
|
31
|
+
type: string
|
|
32
|
+
required: [path, content]
|
|
33
|
+
required: [files]
|
|
34
|
+
|
|
35
|
+
- id: write_files
|
|
36
|
+
type: file
|
|
37
|
+
needs: [design]
|
|
38
|
+
foreach: ${{ steps.design.output.files }}
|
|
39
|
+
op: write
|
|
40
|
+
path: ${{ item.path }}
|
|
41
|
+
content: ${{ item.content }}
|
|
42
|
+
|
|
43
|
+
- id: summary
|
|
44
|
+
type: shell
|
|
45
|
+
needs: [write_files]
|
|
46
|
+
run: |
|
|
47
|
+
echo "Scaffolding complete. Files created:"
|
|
48
|
+
echo "${{ steps.design.output.files.map(f => f.path).join('\n') }}"
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
name: stop-watch
|
|
2
|
+
description: "A simple stopwatch workflow"
|
|
3
|
+
steps:
|
|
4
|
+
- id: get_duration
|
|
5
|
+
type: human
|
|
6
|
+
message: "Please enter the duration (in seconds) for the stopwatch."
|
|
7
|
+
inputType: text
|
|
8
|
+
- id: waiting_period
|
|
9
|
+
type: sleep
|
|
10
|
+
needs: [get_duration]
|
|
11
|
+
duration: ${{ Number(steps.get_duration.output) * 1000 }}
|
|
12
|
+
- id: notify_completion
|
|
13
|
+
type: human
|
|
14
|
+
needs: [waiting_period]
|
|
15
|
+
message: "The stopwatch is complete."
|
|
16
|
+
inputType: confirm
|
|
17
|
+
outputs: {}
|
|
File without changes
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { describe, expect, it
|
|
2
|
-
import { ConfigLoader } from './config-loader';
|
|
1
|
+
import { afterEach, describe, expect, it } from 'bun:test';
|
|
3
2
|
import type { Config } from '../parser/config-schema';
|
|
3
|
+
import { ConfigLoader } from './config-loader';
|
|
4
4
|
|
|
5
5
|
describe('ConfigLoader', () => {
|
|
6
6
|
afterEach(() => {
|