agent-state-machine 2.1.1 → 2.1.3
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/lib/llm.js +78 -5
- package/lib/remote/client.js +1 -1
- package/lib/runtime/agent.js +31 -14
- package/lib/runtime/prompt.js +27 -7
- package/package.json +1 -1
- package/templates/project-builder/agents/assumptions-clarifier.md +6 -9
- package/templates/project-builder/agents/requirements-clarifier.md +7 -9
- package/templates/project-builder/agents/scope-clarifier.md +5 -5
- package/templates/project-builder/agents/security-clarifier.md +6 -13
- package/vercel-server/local-server.js +6 -4
package/lib/llm.js
CHANGED
|
@@ -39,6 +39,81 @@ export function detectAvailableCLIs() {
|
|
|
39
39
|
return available;
|
|
40
40
|
}
|
|
41
41
|
|
|
42
|
+
/**
|
|
43
|
+
* Get response format instructions based on response type
|
|
44
|
+
* Used by buildPrompt to inject appropriate interaction format instructions
|
|
45
|
+
*/
|
|
46
|
+
function getResponseFormatInstructions(responseType) {
|
|
47
|
+
if (responseType === 'choice') {
|
|
48
|
+
return `# Response Format
|
|
49
|
+
|
|
50
|
+
When you need user input, respond with a structured choice:
|
|
51
|
+
|
|
52
|
+
{
|
|
53
|
+
"interact": {
|
|
54
|
+
"type": "choice",
|
|
55
|
+
"slug": "unique-slug",
|
|
56
|
+
"prompt": "Your question here?",
|
|
57
|
+
"options": [
|
|
58
|
+
{ "key": "key1", "label": "Display Label", "description": "Help text" }
|
|
59
|
+
],
|
|
60
|
+
"multiSelect": false,
|
|
61
|
+
"allowCustom": true
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
Rules:
|
|
66
|
+
- slug: unique identifier (e.g., "scope-platform")
|
|
67
|
+
- options: 2-5 choices with key, label, and optional description
|
|
68
|
+
- multiSelect: true allows selecting multiple options
|
|
69
|
+
- allowCustom: true shows "Other" for free-text input
|
|
70
|
+
- Ask ONE question at a time
|
|
71
|
+
`;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
if (responseType === 'confirm') {
|
|
75
|
+
return `# Response Format
|
|
76
|
+
|
|
77
|
+
When you need user confirmation, respond with:
|
|
78
|
+
|
|
79
|
+
{
|
|
80
|
+
"interact": {
|
|
81
|
+
"type": "confirm",
|
|
82
|
+
"slug": "unique-slug",
|
|
83
|
+
"prompt": "Are you sure about X?",
|
|
84
|
+
"confirmLabel": "Yes, proceed",
|
|
85
|
+
"cancelLabel": "No, cancel"
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
`;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
if (responseType === 'text') {
|
|
92
|
+
return `# Response Format
|
|
93
|
+
|
|
94
|
+
When you need text input, respond with:
|
|
95
|
+
|
|
96
|
+
{
|
|
97
|
+
"interact": {
|
|
98
|
+
"type": "text",
|
|
99
|
+
"slug": "unique-slug",
|
|
100
|
+
"prompt": "Please describe X:",
|
|
101
|
+
"placeholder": "Enter details...",
|
|
102
|
+
"validation": { "minLength": 10 }
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
`;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// Default: basic interact format
|
|
109
|
+
return `# Interaction Format
|
|
110
|
+
IF YOU NEED TO ASK THE USER A QUESTION OR REQUEST INPUT, RESPOND WITH EXACTLY:
|
|
111
|
+
{ "interact": "your question here" }
|
|
112
|
+
|
|
113
|
+
Only use this format when you genuinely need user input to proceed.
|
|
114
|
+
`;
|
|
115
|
+
}
|
|
116
|
+
|
|
42
117
|
/**
|
|
43
118
|
* Build the full prompt with steering and context
|
|
44
119
|
*/
|
|
@@ -65,11 +140,9 @@ export function buildPrompt(context, options) {
|
|
|
65
140
|
}
|
|
66
141
|
}
|
|
67
142
|
|
|
68
|
-
// Add
|
|
69
|
-
parts.push(
|
|
70
|
-
parts.push('
|
|
71
|
-
parts.push('{ "interact": "your question here" }\n\n');
|
|
72
|
-
parts.push('Only use this format when you genuinely need user input to proceed.\n\n---\n');
|
|
143
|
+
// Add response format instructions (based on responseType option)
|
|
144
|
+
parts.push(getResponseFormatInstructions(options.responseType));
|
|
145
|
+
parts.push('\n---\n');
|
|
73
146
|
|
|
74
147
|
// Add global steering if available (always first)
|
|
75
148
|
if (context._steering?.global) {
|
package/lib/remote/client.js
CHANGED
|
@@ -193,7 +193,7 @@ export class RemoteClient {
|
|
|
193
193
|
|
|
194
194
|
await this.send({
|
|
195
195
|
...event,
|
|
196
|
-
|
|
196
|
+
_action: 'event', // Use _action for message routing to preserve event.type (interaction type)
|
|
197
197
|
sessionToken: this.sessionToken,
|
|
198
198
|
});
|
|
199
199
|
}
|
package/lib/runtime/agent.js
CHANGED
|
@@ -260,7 +260,8 @@ async function executeMDAgent(runtime, agentPath, name, params, options = {}) {
|
|
|
260
260
|
const fullPrompt = buildPrompt(context, {
|
|
261
261
|
model,
|
|
262
262
|
prompt: interpolatedPrompt,
|
|
263
|
-
includeContext: config.includeContext !== 'false'
|
|
263
|
+
includeContext: config.includeContext !== 'false',
|
|
264
|
+
responseType: config.response
|
|
264
265
|
});
|
|
265
266
|
|
|
266
267
|
await logAgentStart(runtime, name, fullPrompt);
|
|
@@ -270,7 +271,8 @@ async function executeMDAgent(runtime, agentPath, name, params, options = {}) {
|
|
|
270
271
|
response = await llm(context, {
|
|
271
272
|
model: model,
|
|
272
273
|
prompt: interpolatedPrompt,
|
|
273
|
-
includeContext: config.includeContext !== 'false'
|
|
274
|
+
includeContext: config.includeContext !== 'false',
|
|
275
|
+
responseType: config.response
|
|
274
276
|
});
|
|
275
277
|
|
|
276
278
|
// Parse output based on format
|
|
@@ -297,17 +299,26 @@ async function executeMDAgent(runtime, agentPath, name, params, options = {}) {
|
|
|
297
299
|
}
|
|
298
300
|
|
|
299
301
|
// Check for interaction request
|
|
300
|
-
const explicitInteraction =
|
|
301
|
-
config.format === 'interaction' ||
|
|
302
|
-
config.interaction === 'true' ||
|
|
303
|
-
(typeof config.interaction === 'string' && config.interaction.length > 0);
|
|
304
|
-
|
|
305
302
|
const parsedInteraction = parseInteractionRequest(response.text);
|
|
306
303
|
const structuredInteraction =
|
|
307
304
|
config.autoInteract !== 'false' && parsedInteraction.isInteraction;
|
|
308
305
|
|
|
306
|
+
// Check if agent returned an 'interact' object in its JSON response
|
|
307
|
+
const hasInteractKey = output && typeof output === 'object' && output.interact;
|
|
308
|
+
|
|
309
|
+
// Explicit interaction mode (format: interaction OR interaction: true)
|
|
310
|
+
// But only trigger if agent actually wants to interact (has interact key or parsed interaction)
|
|
311
|
+
const explicitInteraction =
|
|
312
|
+
config.format === 'interaction' ||
|
|
313
|
+
((config.interaction === 'true' || (typeof config.interaction === 'string' && config.interaction.length > 0)) &&
|
|
314
|
+
(hasInteractKey || structuredInteraction));
|
|
315
|
+
|
|
309
316
|
if (explicitInteraction || structuredInteraction) {
|
|
317
|
+
// Use interact object if present, otherwise fall back to parsed/raw
|
|
318
|
+
const interactionData = hasInteractKey ? output.interact : (structuredInteraction ? parsedInteraction : null);
|
|
319
|
+
|
|
310
320
|
const slugRaw =
|
|
321
|
+
interactionData?.slug ||
|
|
311
322
|
(typeof config.interaction === 'string' && config.interaction !== 'true'
|
|
312
323
|
? config.interaction
|
|
313
324
|
: null) ||
|
|
@@ -317,13 +328,19 @@ async function executeMDAgent(runtime, agentPath, name, params, options = {}) {
|
|
|
317
328
|
|
|
318
329
|
const slug = sanitizeSlug(slugRaw);
|
|
319
330
|
const targetKey = config.interactionKey || outputKey || slug;
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
const
|
|
331
|
+
|
|
332
|
+
// Build interaction object with full metadata
|
|
333
|
+
const interactionObj = hasInteractKey ? {
|
|
334
|
+
...output.interact,
|
|
335
|
+
slug,
|
|
336
|
+
targetKey
|
|
337
|
+
} : {
|
|
323
338
|
slug,
|
|
324
339
|
targetKey,
|
|
325
|
-
content:
|
|
326
|
-
}
|
|
340
|
+
content: structuredInteraction ? parsedInteraction.question : response.text
|
|
341
|
+
};
|
|
342
|
+
|
|
343
|
+
const userResponse = await handleInteraction(runtime, interactionObj, name);
|
|
327
344
|
|
|
328
345
|
// Return the user's response as the agent result
|
|
329
346
|
if (outputKey) {
|
|
@@ -479,12 +496,12 @@ ${content}
|
|
|
479
496
|
event: 'INTERACTION_REQUESTED',
|
|
480
497
|
slug,
|
|
481
498
|
targetKey,
|
|
482
|
-
question: prompt || content,
|
|
483
499
|
type: interaction.type || 'text',
|
|
484
|
-
prompt,
|
|
500
|
+
prompt: prompt || content,
|
|
485
501
|
options: interaction.options,
|
|
486
502
|
allowCustom: interaction.allowCustom,
|
|
487
503
|
multiSelect: interaction.multiSelect,
|
|
504
|
+
placeholder: interaction.placeholder,
|
|
488
505
|
validation: interaction.validation,
|
|
489
506
|
confirmLabel: interaction.confirmLabel,
|
|
490
507
|
cancelLabel: interaction.cancelLabel,
|
package/lib/runtime/prompt.js
CHANGED
|
@@ -42,12 +42,12 @@ export async function askHuman(question, options = {}) {
|
|
|
42
42
|
event: 'PROMPT_REQUESTED',
|
|
43
43
|
slug,
|
|
44
44
|
targetKey: memoryKey,
|
|
45
|
-
question: prompt,
|
|
46
45
|
type: interaction?.type || 'text',
|
|
47
46
|
prompt,
|
|
48
47
|
options: interaction?.options,
|
|
49
48
|
allowCustom: interaction?.allowCustom,
|
|
50
49
|
multiSelect: interaction?.multiSelect,
|
|
50
|
+
placeholder: interaction?.placeholder,
|
|
51
51
|
validation: interaction?.validation,
|
|
52
52
|
confirmLabel: interaction?.confirmLabel,
|
|
53
53
|
cancelLabel: interaction?.cancelLabel,
|
|
@@ -57,7 +57,7 @@ export async function askHuman(question, options = {}) {
|
|
|
57
57
|
// Check if we're in TTY mode (interactive terminal)
|
|
58
58
|
if (process.stdin.isTTY && process.stdout.isTTY) {
|
|
59
59
|
// Interactive mode - prompt directly, with remote support
|
|
60
|
-
const answer = await askQuestionWithRemote(runtime, question, slug, memoryKey);
|
|
60
|
+
const answer = await askQuestionWithRemote(runtime, question, slug, memoryKey, interaction);
|
|
61
61
|
console.log('');
|
|
62
62
|
|
|
63
63
|
const normalizedAnswer = normalizePromptAnswer(answer);
|
|
@@ -115,7 +115,7 @@ ${question}
|
|
|
115
115
|
* Interactive terminal question with remote support
|
|
116
116
|
* Allows both local TTY input and remote browser responses
|
|
117
117
|
*/
|
|
118
|
-
function askQuestionWithRemote(runtime, question, slug, memoryKey) {
|
|
118
|
+
function askQuestionWithRemote(runtime, question, slug, memoryKey, interaction = null) {
|
|
119
119
|
return new Promise((resolve) => {
|
|
120
120
|
let resolved = false;
|
|
121
121
|
|
|
@@ -146,13 +146,33 @@ function askQuestionWithRemote(runtime, question, slug, memoryKey) {
|
|
|
146
146
|
});
|
|
147
147
|
|
|
148
148
|
// Show remote URL if available
|
|
149
|
-
let
|
|
149
|
+
let promptText = `\n${C.cyan}${C.bold}${question}${C.reset}`;
|
|
150
|
+
|
|
151
|
+
// Show placeholder if provided
|
|
152
|
+
if (interaction?.placeholder) {
|
|
153
|
+
promptText += `\n${C.dim}(e.g., ${interaction.placeholder})${C.reset}`;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
// Show validation hints if provided
|
|
157
|
+
if (interaction?.validation) {
|
|
158
|
+
const hints = [];
|
|
159
|
+
if (interaction.validation.minLength) {
|
|
160
|
+
hints.push(`min ${interaction.validation.minLength} chars`);
|
|
161
|
+
}
|
|
162
|
+
if (interaction.validation.maxLength) {
|
|
163
|
+
hints.push(`max ${interaction.validation.maxLength} chars`);
|
|
164
|
+
}
|
|
165
|
+
if (hints.length > 0) {
|
|
166
|
+
promptText += `\n${C.dim}[${hints.join(', ')}]${C.reset}`;
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
|
|
150
170
|
if (runtime.remoteEnabled && runtime.remoteUrl) {
|
|
151
|
-
|
|
171
|
+
promptText += `\n${C.dim}(Remote: ${runtime.remoteUrl})${C.reset}`;
|
|
152
172
|
}
|
|
153
|
-
|
|
173
|
+
promptText += `\n${C.yellow}> ${C.reset}`;
|
|
154
174
|
|
|
155
|
-
rl.question(
|
|
175
|
+
rl.question(promptText, (answer) => {
|
|
156
176
|
if (resolved) return;
|
|
157
177
|
cleanup();
|
|
158
178
|
rl.close();
|
package/package.json
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
model: med
|
|
3
3
|
format: json
|
|
4
4
|
interaction: true
|
|
5
|
+
response: choice
|
|
5
6
|
---
|
|
6
7
|
|
|
7
8
|
# Assumptions Clarifier Agent
|
|
@@ -22,26 +23,22 @@ Identify implicit assumptions that could impact the project. Consider:
|
|
|
22
23
|
|
|
23
24
|
**Technical Assumptions:**
|
|
24
25
|
- Technology stack preferences
|
|
25
|
-
- Development environment
|
|
26
26
|
- Existing infrastructure
|
|
27
27
|
- Third-party dependencies
|
|
28
28
|
|
|
29
29
|
**Business Assumptions:**
|
|
30
30
|
- Timeline expectations
|
|
31
|
-
- Budget constraints
|
|
32
31
|
- Team composition/skills
|
|
33
|
-
- Stakeholder availability
|
|
34
32
|
|
|
35
33
|
**Domain Assumptions:**
|
|
36
34
|
- Industry regulations
|
|
37
35
|
- Compliance requirements
|
|
38
|
-
- Domain-specific constraints
|
|
39
36
|
|
|
40
|
-
If assumptions need validation, ask
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
37
|
+
If assumptions need validation, ask ONE question. Example slugs:
|
|
38
|
+
- "assume-stack": Technology stack preference
|
|
39
|
+
- "assume-timeline": Development approach (MVP, production-ready, iterative)
|
|
40
|
+
- "assume-codebase": Starting point (greenfield, existing code, migration)
|
|
41
|
+
- "assume-infra": Infrastructure constraints
|
|
45
42
|
|
|
46
43
|
If assumptions are clear, return:
|
|
47
44
|
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
model: med
|
|
3
3
|
format: json
|
|
4
4
|
interaction: true
|
|
5
|
+
response: choice
|
|
5
6
|
---
|
|
6
7
|
|
|
7
8
|
# Requirements Clarifier Agent
|
|
@@ -23,19 +24,16 @@ Based on the project description and scope, identify requirements that need clar
|
|
|
23
24
|
- Core features and user stories
|
|
24
25
|
- Data models and relationships
|
|
25
26
|
- User workflows and interactions
|
|
26
|
-
- Input/output specifications
|
|
27
27
|
|
|
28
28
|
**Non-Functional Requirements:**
|
|
29
29
|
- Performance expectations
|
|
30
|
-
- Scalability needs
|
|
31
|
-
- Reliability/uptime requirements
|
|
32
|
-
- Accessibility requirements
|
|
30
|
+
- Scalability and reliability needs
|
|
33
31
|
|
|
34
|
-
If requirements need clarification, ask
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
32
|
+
If requirements need clarification, ask ONE question. Example slugs:
|
|
33
|
+
- "req-storage": Data storage approach (local, cloud, hybrid)
|
|
34
|
+
- "req-auth": Authentication method (none, basic, OAuth, MFA)
|
|
35
|
+
- "req-offline": Offline capability needs
|
|
36
|
+
- "req-realtime": Real-time features needed
|
|
39
37
|
|
|
40
38
|
If requirements are clear, return:
|
|
41
39
|
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
model: med
|
|
3
3
|
format: json
|
|
4
4
|
interaction: true
|
|
5
|
+
response: choice
|
|
5
6
|
---
|
|
6
7
|
|
|
7
8
|
# Scope Clarifier Agent
|
|
@@ -23,11 +24,10 @@ Analyze the project description and determine if the scope is clear. Consider:
|
|
|
23
24
|
- Platform/environment constraints
|
|
24
25
|
- Integration requirements
|
|
25
26
|
|
|
26
|
-
If the scope is unclear
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
}
|
|
27
|
+
If the scope is unclear, ask ONE clarifying question. Example slugs:
|
|
28
|
+
- "scope-platform": Target platform (web, mobile, desktop, API)
|
|
29
|
+
- "scope-scale": User scale (personal, team, enterprise)
|
|
30
|
+
- "scope-integrations": External integrations needed
|
|
31
31
|
|
|
32
32
|
If the scope is sufficiently clear, return the scope summary:
|
|
33
33
|
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
model: med
|
|
3
3
|
format: json
|
|
4
4
|
interaction: true
|
|
5
|
+
response: choice
|
|
5
6
|
---
|
|
6
7
|
|
|
7
8
|
# Security Clarifier Agent
|
|
@@ -24,28 +25,20 @@ Analyze the project for security implications. Consider:
|
|
|
24
25
|
**Data Security:**
|
|
25
26
|
- Sensitive data handling (PII, financial, health)
|
|
26
27
|
- Data encryption requirements
|
|
27
|
-
- Data retention policies
|
|
28
28
|
|
|
29
29
|
**Access Control:**
|
|
30
30
|
- Authentication requirements
|
|
31
31
|
- Authorization model
|
|
32
|
-
- Role-based access needs
|
|
33
32
|
|
|
34
33
|
**Compliance:**
|
|
35
34
|
- Regulatory requirements (GDPR, HIPAA, PCI-DSS)
|
|
36
|
-
- Industry standards
|
|
37
35
|
- Audit requirements
|
|
38
36
|
|
|
39
|
-
|
|
40
|
-
-
|
|
41
|
-
-
|
|
42
|
-
-
|
|
43
|
-
|
|
44
|
-
If security requirements need clarification, ask using the interact format:
|
|
45
|
-
|
|
46
|
-
{
|
|
47
|
-
"interact": "Please clarify security requirements:\n\n1. Sensitive Data:\n - A: No sensitive data handled\n - B: Personal information (names, emails)\n - C: Financial data (payments, transactions)\n - D: Health/medical data\n - E: Other regulated data\n\n2. Compliance Requirements:\n - A: No specific compliance needed\n - B: GDPR (EU data protection)\n - C: HIPAA (healthcare)\n - D: PCI-DSS (payment cards)\n - E: SOC2 / enterprise security\n\n3. Authentication Level:\n - A: Basic (username/password)\n - B: Enhanced (MFA, SSO)\n - C: Enterprise (LDAP, SAML)\n\nPlease respond with your choices and details:"
|
|
48
|
-
}
|
|
37
|
+
If security requirements need clarification, ask ONE question. Example slugs:
|
|
38
|
+
- "sec-data": Sensitive data types handled (none, PII, financial, health)
|
|
39
|
+
- "sec-compliance": Compliance requirements (GDPR, HIPAA, PCI-DSS, SOC2)
|
|
40
|
+
- "sec-auth": Authentication level (basic, MFA, SSO, enterprise)
|
|
41
|
+
- "sec-audit": Audit/logging requirements
|
|
49
42
|
|
|
50
43
|
If security requirements are clear, return:
|
|
51
44
|
|
|
@@ -112,13 +112,15 @@ function sendJson(res, status, data) {
|
|
|
112
112
|
*/
|
|
113
113
|
async function handleCliPost(req, res) {
|
|
114
114
|
const body = await parseBody(req);
|
|
115
|
-
const {
|
|
115
|
+
const { sessionToken } = body;
|
|
116
|
+
// Support both _action (new) and type (legacy) for message routing
|
|
117
|
+
const action = body._action || body.type;
|
|
116
118
|
|
|
117
119
|
if (!sessionToken) {
|
|
118
120
|
return sendJson(res, 400, { error: 'Missing sessionToken' });
|
|
119
121
|
}
|
|
120
122
|
|
|
121
|
-
switch (
|
|
123
|
+
switch (action) {
|
|
122
124
|
case 'session_init': {
|
|
123
125
|
const { workflowName, history } = body;
|
|
124
126
|
createSession(sessionToken, { workflowName, history });
|
|
@@ -152,7 +154,7 @@ async function handleCliPost(req, res) {
|
|
|
152
154
|
...eventData,
|
|
153
155
|
};
|
|
154
156
|
delete historyEvent.sessionToken;
|
|
155
|
-
delete historyEvent.type
|
|
157
|
+
delete historyEvent._action; // Remove routing field, preserve type (interaction type)
|
|
156
158
|
|
|
157
159
|
// Add to history
|
|
158
160
|
session.history.unshift(historyEvent);
|
|
@@ -179,7 +181,7 @@ async function handleCliPost(req, res) {
|
|
|
179
181
|
}
|
|
180
182
|
|
|
181
183
|
default:
|
|
182
|
-
return sendJson(res, 400, { error: `Unknown
|
|
184
|
+
return sendJson(res, 400, { error: `Unknown action: ${action}` });
|
|
183
185
|
}
|
|
184
186
|
}
|
|
185
187
|
|