@compilr-dev/sdk 0.10.10 → 0.10.12
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/capabilities/packs.js +8 -2
- package/dist/skills/software-skills.js +2 -1
- package/dist/system-prompt/modules.js +1 -1
- package/dist/team/skill-requirements.js +15 -3
- package/dist/team/tool-config.js +1 -0
- package/dist/team/types.js +2 -2
- package/dist/tools/interactive-flow-tool.js +138 -31
- package/package.json +1 -1
|
@@ -53,11 +53,17 @@ export const CAPABILITY_PACKS = {
|
|
|
53
53
|
interaction: {
|
|
54
54
|
id: 'interaction',
|
|
55
55
|
label: 'User Interaction',
|
|
56
|
-
tools: [
|
|
56
|
+
tools: [
|
|
57
|
+
'ask_user',
|
|
58
|
+
'ask_user_simple',
|
|
59
|
+
'propose_alternatives',
|
|
60
|
+
'suggest',
|
|
61
|
+
'build_interactive_flow',
|
|
62
|
+
],
|
|
57
63
|
readOnly: true,
|
|
58
64
|
promptModules: [],
|
|
59
65
|
estimatedPromptTokens: 0,
|
|
60
|
-
estimatedToolTokens:
|
|
66
|
+
estimatedToolTokens: 1100,
|
|
61
67
|
},
|
|
62
68
|
coordinator: {
|
|
63
69
|
id: 'coordinator',
|
|
@@ -148,13 +148,14 @@ For complex features:
|
|
|
148
148
|
|
|
149
149
|
- **ask_user_simple**: For single-choice questions (preferred for most interactions)
|
|
150
150
|
- **ask_user**: For multi-question batches (when gathering related info)
|
|
151
|
+
- **build_interactive_flow**: For decisions with 2+ branching considerations the user benefits from exploring visually (navigable tree with Back/forward; agent receives the full reasoning path back)
|
|
151
152
|
- **backlog_read**: Use id param to get specific item, use search/filters otherwise
|
|
152
153
|
- **backlog_write**: Update items after refinement decisions
|
|
153
154
|
- **todo_write**: Track your progress through refinement
|
|
154
155
|
|
|
155
156
|
## RULES
|
|
156
157
|
|
|
157
|
-
1. ALWAYS use ask_user_simple or
|
|
158
|
+
1. ALWAYS use ask_user_simple, ask_user, or build_interactive_flow for decisions - NEVER ask questions in plain text
|
|
158
159
|
2. Keep backlog reads efficient - use filters and limits
|
|
159
160
|
3. One refinement focus at a time
|
|
160
161
|
4. Update backlog with backlog_write after each decision
|
|
@@ -140,7 +140,7 @@ IMPORTANT: Tool names are lowercase with underscores.
|
|
|
140
140
|
- **File operations**: read_file, write_file, edit, glob, grep
|
|
141
141
|
- **Shell**: bash (use run_in_background=true for long-running), bash_output, kill_shell
|
|
142
142
|
- **Task management**: todo_write, todo_read
|
|
143
|
-
- **User interaction**:
|
|
143
|
+
- **User interaction**: ask_user_simple (single question, small models), ask_user (1-5 questions), propose_alternatives (2-3 options with pros/cons), build_interactive_flow (navigable decision tree — use when a choice has 2+ branching considerations the user benefits from exploring visually with Back/forward)
|
|
144
144
|
- **Sub-agents**: task (types: explore, code-review, plan, debug, test-runner, refactor, security-audit, general)`,
|
|
145
145
|
};
|
|
146
146
|
/**
|
|
@@ -46,12 +46,18 @@ export const SKILL_REQUIREMENTS = {
|
|
|
46
46
|
// Planning skills
|
|
47
47
|
planning: {
|
|
48
48
|
required: ['read_file', 'glob'],
|
|
49
|
-
optional: ['ask_user', 'ask_user_simple', 'todo_write'],
|
|
49
|
+
optional: ['ask_user', 'ask_user_simple', 'build_interactive_flow', 'todo_write'],
|
|
50
50
|
reason: 'Needs to understand codebase to plan effectively',
|
|
51
51
|
},
|
|
52
52
|
design: {
|
|
53
53
|
required: ['ask_user', 'workitem_add'],
|
|
54
|
-
optional: [
|
|
54
|
+
optional: [
|
|
55
|
+
'propose_alternatives',
|
|
56
|
+
'build_interactive_flow',
|
|
57
|
+
'detect_project',
|
|
58
|
+
'document_save',
|
|
59
|
+
'edit',
|
|
60
|
+
],
|
|
55
61
|
reason: 'Needs to gather requirements and create backlog',
|
|
56
62
|
},
|
|
57
63
|
refine: {
|
|
@@ -72,7 +78,13 @@ export const SKILL_REQUIREMENTS = {
|
|
|
72
78
|
// Documentation skills
|
|
73
79
|
architecture: {
|
|
74
80
|
required: ['read_file', 'write_file', 'edit'],
|
|
75
|
-
optional: [
|
|
81
|
+
optional: [
|
|
82
|
+
'backlog_read',
|
|
83
|
+
'ask_user',
|
|
84
|
+
'propose_alternatives',
|
|
85
|
+
'build_interactive_flow',
|
|
86
|
+
'glob',
|
|
87
|
+
],
|
|
76
88
|
reason: 'Creates architecture documents',
|
|
77
89
|
},
|
|
78
90
|
prd: {
|
package/dist/team/tool-config.js
CHANGED
|
@@ -22,6 +22,7 @@ const TOOL_NAMES = {
|
|
|
22
22
|
ASK_USER: 'ask_user',
|
|
23
23
|
ASK_USER_SIMPLE: 'ask_user_simple',
|
|
24
24
|
PROPOSE_ALTERNATIVES: 'propose_alternatives',
|
|
25
|
+
BUILD_INTERACTIVE_FLOW: 'build_interactive_flow',
|
|
25
26
|
// Delegation
|
|
26
27
|
DELEGATE: 'delegate',
|
|
27
28
|
DELEGATE_BACKGROUND: 'delegate_background',
|
package/dist/team/types.js
CHANGED
|
@@ -115,7 +115,7 @@ You are the **Project Manager (PM)** in this multi-agent development team. You s
|
|
|
115
115
|
|
|
116
116
|
**Direct tools** (call by name):
|
|
117
117
|
- \`todo_write\`, \`todo_read\` - Task tracking
|
|
118
|
-
- \`ask_user\`, \`suggest\` - User interaction
|
|
118
|
+
- \`ask_user_simple\`, \`ask_user\`, \`propose_alternatives\`, \`build_interactive_flow\`, \`suggest\` - User interaction
|
|
119
119
|
- \`handoff\` - Hand off to another specialist
|
|
120
120
|
|
|
121
121
|
**Specialized tools** (call via \`use_tool\`):
|
|
@@ -601,7 +601,7 @@ You are the **Business Analyst** in this multi-agent development team. You trans
|
|
|
601
601
|
|
|
602
602
|
**Direct tools** (call by name):
|
|
603
603
|
- \`todo_write\`, \`todo_read\` - Task tracking
|
|
604
|
-
- \`ask_user\`, \`suggest\` - User interaction
|
|
604
|
+
- \`ask_user_simple\`, \`ask_user\`, \`propose_alternatives\`, \`build_interactive_flow\`, \`suggest\` - User interaction
|
|
605
605
|
- \`handoff\` - Hand off to another specialist
|
|
606
606
|
|
|
607
607
|
**Specialized tools** (call via \`use_tool\`):
|
|
@@ -38,6 +38,16 @@ const SUMMARY_RENDERERS = ['compact', 'detailed'];
|
|
|
38
38
|
* slug doesn't map to a component.
|
|
39
39
|
*/
|
|
40
40
|
const ICON_NAME_RE = /^[a-z][a-z0-9]*(-[a-z0-9]+)*$/;
|
|
41
|
+
/* eslint-disable @typescript-eslint/no-unnecessary-condition, @typescript-eslint/no-unnecessary-type-conversion --
|
|
42
|
+
* The entire validator section runs DEFENSIVE runtime checks against agent
|
|
43
|
+
* JSON arriving over IPC. TypeScript types are lies at this boundary — an
|
|
44
|
+
* agent's malformed input can have any field missing or wrong-typed, and we
|
|
45
|
+
* must return structured errors rather than throw. The "always-falsy" and
|
|
46
|
+
* "redundant String()" warnings would be true if we only saw type-safe
|
|
47
|
+
* callers, but our caller is `JSON.parse(IPC payload)` cast to Flow. The
|
|
48
|
+
* pragmatic alternative (passing `unknown` through every helper signature)
|
|
49
|
+
* would be a much larger refactor for no behavioral gain.
|
|
50
|
+
*/
|
|
41
51
|
/**
|
|
42
52
|
* Validate a flow. Accepts `unknown` because inputs flow in as JSON from the
|
|
43
53
|
* agent and TS types are lies at the boundary — runtime defensive checks
|
|
@@ -75,10 +85,18 @@ export function validateFlow(flowInput) {
|
|
|
75
85
|
// 2. Each node's id must match the key it lives under
|
|
76
86
|
for (const id of nodeIds) {
|
|
77
87
|
const node = flow.nodes[id];
|
|
88
|
+
if (!node || typeof node !== 'object') {
|
|
89
|
+
errors.push({
|
|
90
|
+
code: 'INVALID_NODE_TYPE',
|
|
91
|
+
message: `flow.nodes['${id}'] is null or not an object`,
|
|
92
|
+
nodeId: id,
|
|
93
|
+
});
|
|
94
|
+
continue;
|
|
95
|
+
}
|
|
78
96
|
if (node.id !== id) {
|
|
79
97
|
errors.push({
|
|
80
98
|
code: 'DUPLICATE_NODE_ID',
|
|
81
|
-
message: `Node key '${id}' does not match node.id '${node.id}'`,
|
|
99
|
+
message: `Node key '${id}' does not match node.id '${String(node.id)}'`,
|
|
82
100
|
nodeId: id,
|
|
83
101
|
});
|
|
84
102
|
}
|
|
@@ -86,6 +104,8 @@ export function validateFlow(flowInput) {
|
|
|
86
104
|
// 3. Per-node validation: type, references, icons, renderers, conditions
|
|
87
105
|
for (const id of nodeIds) {
|
|
88
106
|
const node = flow.nodes[id];
|
|
107
|
+
if (!node || typeof node !== 'object')
|
|
108
|
+
continue; // already flagged above
|
|
89
109
|
validateNode(node, flow.nodes, errors);
|
|
90
110
|
}
|
|
91
111
|
// 4. Reachability — warn on orphans
|
|
@@ -125,6 +145,16 @@ function validateNode(node, allNodes, errors) {
|
|
|
125
145
|
}
|
|
126
146
|
}
|
|
127
147
|
function validateQuestion(node, allNodes, errors) {
|
|
148
|
+
// Defensive: agent JSON may omit required nested fields. Surface as
|
|
149
|
+
// structured errors rather than letting the validator throw.
|
|
150
|
+
if (!node.input || typeof node.input !== 'object') {
|
|
151
|
+
errors.push({
|
|
152
|
+
code: 'INVALID_NODE_TYPE',
|
|
153
|
+
message: `Question node '${node.id}' is missing the required 'input' object (must contain 'mode' and choices/options/etc.)`,
|
|
154
|
+
nodeId: node.id,
|
|
155
|
+
});
|
|
156
|
+
return;
|
|
157
|
+
}
|
|
128
158
|
// Input mode must have a valid renderer if specified. The lookup CAN miss
|
|
129
159
|
// at runtime if the agent passed an unknown mode in the JSON — TS narrowing
|
|
130
160
|
// doesn't help us across the boundary, hence the explicit widened type.
|
|
@@ -132,7 +162,7 @@ function validateQuestion(node, allNodes, errors) {
|
|
|
132
162
|
if (!validRenderers) {
|
|
133
163
|
errors.push({
|
|
134
164
|
code: 'INVALID_NODE_TYPE',
|
|
135
|
-
message: `Unknown question input mode '${node.input.mode}'`,
|
|
165
|
+
message: `Unknown question input mode '${String(node.input.mode)}' for node '${node.id}' (expected: single, multi, text, or proposal)`,
|
|
136
166
|
nodeId: node.id,
|
|
137
167
|
});
|
|
138
168
|
return;
|
|
@@ -144,23 +174,67 @@ function validateQuestion(node, allNodes, errors) {
|
|
|
144
174
|
nodeId: node.id,
|
|
145
175
|
});
|
|
146
176
|
}
|
|
147
|
-
// Validate
|
|
177
|
+
// Validate choices/options arrays are present and well-formed
|
|
148
178
|
if (node.input.mode === 'single' || node.input.mode === 'multi') {
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
179
|
+
if (!Array.isArray(node.input.choices)) {
|
|
180
|
+
errors.push({
|
|
181
|
+
code: 'INVALID_NODE_TYPE',
|
|
182
|
+
message: `Question '${node.id}' with mode '${node.input.mode}' requires a 'choices' array`,
|
|
183
|
+
nodeId: node.id,
|
|
184
|
+
});
|
|
185
|
+
}
|
|
186
|
+
else {
|
|
187
|
+
for (const choice of node.input.choices) {
|
|
188
|
+
if (!choice || typeof choice !== 'object')
|
|
189
|
+
continue;
|
|
190
|
+
if (choice.icon !== undefined)
|
|
191
|
+
checkIcon(choice.icon, node.id, errors);
|
|
192
|
+
}
|
|
152
193
|
}
|
|
153
194
|
}
|
|
154
195
|
else if (node.input.mode === 'proposal') {
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
196
|
+
if (!Array.isArray(node.input.options)) {
|
|
197
|
+
errors.push({
|
|
198
|
+
code: 'INVALID_NODE_TYPE',
|
|
199
|
+
message: `Question '${node.id}' with mode 'proposal' requires an 'options' array`,
|
|
200
|
+
nodeId: node.id,
|
|
201
|
+
});
|
|
202
|
+
}
|
|
203
|
+
else {
|
|
204
|
+
for (const opt of node.input.options) {
|
|
205
|
+
if (!opt || typeof opt !== 'object')
|
|
206
|
+
continue;
|
|
207
|
+
if (opt.icon !== undefined)
|
|
208
|
+
checkIcon(opt.icon, node.id, errors);
|
|
209
|
+
}
|
|
158
210
|
}
|
|
159
211
|
}
|
|
160
|
-
// Validate next references
|
|
212
|
+
// Validate next references (defensive — `next` might be undefined)
|
|
213
|
+
if (node.next === undefined || node.next === null) {
|
|
214
|
+
errors.push({
|
|
215
|
+
code: 'UNRESOLVED_NEXT',
|
|
216
|
+
message: `Question node '${node.id}' is missing the required 'next' field (string nodeId or { byAnswer: ... })`,
|
|
217
|
+
nodeId: node.id,
|
|
218
|
+
});
|
|
219
|
+
return;
|
|
220
|
+
}
|
|
161
221
|
validateNextRef(node.next, node, allNodes, errors);
|
|
162
222
|
}
|
|
163
223
|
function validateInfo(node, allNodes, errors) {
|
|
224
|
+
if (typeof node.title !== 'string' || node.title.length === 0) {
|
|
225
|
+
errors.push({
|
|
226
|
+
code: 'INVALID_NODE_TYPE',
|
|
227
|
+
message: `Info node '${node.id}' is missing required 'title' string`,
|
|
228
|
+
nodeId: node.id,
|
|
229
|
+
});
|
|
230
|
+
}
|
|
231
|
+
if (typeof node.body !== 'string') {
|
|
232
|
+
errors.push({
|
|
233
|
+
code: 'INVALID_NODE_TYPE',
|
|
234
|
+
message: `Info node '${node.id}' is missing required 'body' string (markdown content)`,
|
|
235
|
+
nodeId: node.id,
|
|
236
|
+
});
|
|
237
|
+
}
|
|
164
238
|
if (node.render && !INFO_RENDERERS.includes(node.render)) {
|
|
165
239
|
errors.push({
|
|
166
240
|
code: 'INVALID_RENDERER',
|
|
@@ -168,31 +242,56 @@ function validateInfo(node, allNodes, errors) {
|
|
|
168
242
|
nodeId: node.id,
|
|
169
243
|
});
|
|
170
244
|
}
|
|
245
|
+
if (node.next === undefined || node.next === null) {
|
|
246
|
+
errors.push({
|
|
247
|
+
code: 'UNRESOLVED_NEXT',
|
|
248
|
+
message: `Info node '${node.id}' is missing the required 'next' field`,
|
|
249
|
+
nodeId: node.id,
|
|
250
|
+
});
|
|
251
|
+
return;
|
|
252
|
+
}
|
|
171
253
|
validateNextRef(node.next, node, allNodes, errors);
|
|
172
254
|
}
|
|
173
255
|
function validateBranch(node, allNodes, errors) {
|
|
174
|
-
//
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
256
|
+
// Defensive: routes might be missing or non-array
|
|
257
|
+
if (!Array.isArray(node.routes)) {
|
|
258
|
+
errors.push({
|
|
259
|
+
code: 'INVALID_NODE_TYPE',
|
|
260
|
+
message: `Branch node '${node.id}' requires a 'routes' array (each item: { when: ..., goto: nodeId })`,
|
|
261
|
+
nodeId: node.id,
|
|
262
|
+
});
|
|
263
|
+
}
|
|
264
|
+
else {
|
|
265
|
+
for (let i = 0; i < node.routes.length; i++) {
|
|
266
|
+
const route = node.routes[i];
|
|
267
|
+
if (!route || typeof route !== 'object') {
|
|
268
|
+
errors.push({
|
|
269
|
+
code: 'INVALID_CONDITION',
|
|
270
|
+
message: `Branch route ${String(i)} on node '${node.id}' is null or not an object`,
|
|
271
|
+
nodeId: node.id,
|
|
272
|
+
});
|
|
273
|
+
continue;
|
|
274
|
+
}
|
|
275
|
+
if (!isValidCondition(route.when)) {
|
|
276
|
+
errors.push({
|
|
277
|
+
code: 'INVALID_CONDITION',
|
|
278
|
+
message: `Branch route ${String(i)} has an invalid condition shape`,
|
|
279
|
+
nodeId: node.id,
|
|
280
|
+
});
|
|
281
|
+
}
|
|
282
|
+
if (typeof route.goto !== 'string' || !(route.goto in allNodes)) {
|
|
283
|
+
errors.push({
|
|
284
|
+
code: 'UNRESOLVED_BRANCH',
|
|
285
|
+
message: `Branch route ${String(i)} goto '${String(route.goto)}' is not defined in flow.nodes`,
|
|
286
|
+
nodeId: node.id,
|
|
287
|
+
});
|
|
288
|
+
}
|
|
190
289
|
}
|
|
191
290
|
}
|
|
192
|
-
if (!node.default || !(node.default in allNodes)) {
|
|
291
|
+
if (!node.default || typeof node.default !== 'string' || !(node.default in allNodes)) {
|
|
193
292
|
errors.push({
|
|
194
293
|
code: 'UNRESOLVED_BRANCH',
|
|
195
|
-
message: `Branch default '${node.default}' is not defined in flow.nodes`,
|
|
294
|
+
message: `Branch default '${String(node.default)}' is not defined in flow.nodes`,
|
|
196
295
|
nodeId: node.id,
|
|
197
296
|
});
|
|
198
297
|
}
|
|
@@ -218,12 +317,20 @@ function validateNextRef(next, node, allNodes, errors) {
|
|
|
218
317
|
}
|
|
219
318
|
return;
|
|
220
319
|
}
|
|
221
|
-
// byAnswer object
|
|
320
|
+
// byAnswer object — defensive on shape
|
|
321
|
+
if (!next || typeof next !== 'object' || !next.byAnswer || typeof next.byAnswer !== 'object') {
|
|
322
|
+
errors.push({
|
|
323
|
+
code: 'UNRESOLVED_NEXT',
|
|
324
|
+
message: `Node '${node.id}' next is not a valid NextRef (expected string nodeId or { byAnswer: { ... } })`,
|
|
325
|
+
nodeId: node.id,
|
|
326
|
+
});
|
|
327
|
+
return;
|
|
328
|
+
}
|
|
222
329
|
for (const [answer, target] of Object.entries(next.byAnswer)) {
|
|
223
|
-
if (!(target in allNodes)) {
|
|
330
|
+
if (typeof target !== 'string' || !(target in allNodes)) {
|
|
224
331
|
errors.push({
|
|
225
332
|
code: 'UNRESOLVED_NEXT',
|
|
226
|
-
message: `Node '${node.id}' next.byAnswer['${answer}'] references '${target}' which is not defined in flow.nodes`,
|
|
333
|
+
message: `Node '${node.id}' next.byAnswer['${answer}'] references '${String(target)}' which is not defined in flow.nodes`,
|
|
227
334
|
nodeId: node.id,
|
|
228
335
|
});
|
|
229
336
|
}
|