prompt-language-shell 0.4.8 → 0.4.9
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/config/EXECUTE.md +279 -0
- package/dist/config/PLAN.md +19 -6
- package/dist/handlers/answer.js +13 -20
- package/dist/handlers/command.js +26 -30
- package/dist/handlers/config.js +9 -18
- package/dist/handlers/execute.js +38 -0
- package/dist/handlers/execution.js +66 -81
- package/dist/handlers/introspect.js +13 -20
- package/dist/handlers/plan.js +31 -34
- package/dist/services/anthropic.js +26 -1
- package/dist/services/colors.js +1 -1
- package/dist/services/components.js +17 -0
- package/dist/services/messages.js +1 -0
- package/dist/services/shell.js +117 -0
- package/dist/services/tool-registry.js +5 -0
- package/dist/services/utils.js +21 -0
- package/dist/tools/execute.tool.js +44 -0
- package/dist/types/handlers.js +1 -0
- package/dist/types/types.js +1 -0
- package/dist/ui/Component.js +6 -0
- package/dist/ui/Confirm.js +2 -2
- package/dist/ui/Execute.js +217 -0
- package/dist/ui/Main.js +30 -69
- package/dist/ui/Spinner.js +10 -5
- package/package.json +7 -7
|
@@ -1,88 +1,73 @@
|
|
|
1
1
|
import { ComponentName, FeedbackType, TaskType } from '../types/types.js';
|
|
2
|
-
import { createAnswerDefinition, createConfigDefinitionWithKeys, createFeedback, createIntrospectDefinition, markAsDone, } from '../services/components.js';
|
|
3
|
-
import { createConfigExecutionAbortedHandler, createConfigExecutionFinishedHandler, } from './config.js';
|
|
2
|
+
import { createAnswerDefinition, createConfigDefinitionWithKeys, createExecuteDefinition, createFeedback, createIntrospectDefinition, markAsDone, } from '../services/components.js';
|
|
4
3
|
import { getCancellationMessage } from '../services/messages.js';
|
|
5
4
|
import { exitApp } from '../services/process.js';
|
|
6
5
|
import { withQueueHandler } from '../services/queue.js';
|
|
6
|
+
import { createConfigExecutionAbortedHandler, createConfigExecutionFinishedHandler, } from './config.js';
|
|
7
7
|
/**
|
|
8
|
-
* Creates execution
|
|
9
|
-
*/
|
|
10
|
-
export function createExecutionConfirmedHandler(timelineRef, addToTimeline, service, handleIntrospectError, handleIntrospectComplete, handleIntrospectAborted, handleAnswerError, handleAnswerComplete, handleAnswerAborted, setQueue) {
|
|
11
|
-
return () => withQueueHandler(ComponentName.Confirm, (first) => {
|
|
12
|
-
// Find the most recent Plan in timeline to get tasks
|
|
13
|
-
const currentTimeline = timelineRef.current;
|
|
14
|
-
const lastPlanIndex = [...currentTimeline]
|
|
15
|
-
.reverse()
|
|
16
|
-
.findIndex((item) => item.name === ComponentName.Plan);
|
|
17
|
-
const lastPlan = lastPlanIndex >= 0
|
|
18
|
-
? currentTimeline[currentTimeline.length - 1 - lastPlanIndex]
|
|
19
|
-
: null;
|
|
20
|
-
const tasks = lastPlan?.name === ComponentName.Plan &&
|
|
21
|
-
Array.isArray(lastPlan.props.tasks)
|
|
22
|
-
? lastPlan.props.tasks
|
|
23
|
-
: [];
|
|
24
|
-
const allIntrospect = tasks.every((task) => task.type === TaskType.Introspect);
|
|
25
|
-
const allAnswer = tasks.every((task) => task.type === TaskType.Answer);
|
|
26
|
-
const allConfig = tasks.every((task) => task.type === TaskType.Config);
|
|
27
|
-
if (allIntrospect && tasks.length > 0) {
|
|
28
|
-
// Execute introspection
|
|
29
|
-
addToTimeline(markAsDone(first));
|
|
30
|
-
return [
|
|
31
|
-
createIntrospectDefinition(tasks, service, handleIntrospectError, handleIntrospectComplete, handleIntrospectAborted),
|
|
32
|
-
];
|
|
33
|
-
}
|
|
34
|
-
else if (allAnswer && tasks.length > 0) {
|
|
35
|
-
// Execute answer - extract question from first task
|
|
36
|
-
const question = tasks[0].action;
|
|
37
|
-
addToTimeline(markAsDone(first));
|
|
38
|
-
return [
|
|
39
|
-
createAnswerDefinition(question, service, handleAnswerError, handleAnswerComplete, handleAnswerAborted),
|
|
40
|
-
];
|
|
41
|
-
}
|
|
42
|
-
else if (allConfig && tasks.length > 0) {
|
|
43
|
-
// Execute config - extract keys from task params
|
|
44
|
-
const keys = tasks
|
|
45
|
-
.map((task) => task.params?.key)
|
|
46
|
-
.filter((key) => typeof key === 'string');
|
|
47
|
-
addToTimeline(markAsDone(first));
|
|
48
|
-
// Create handlers with keys for proper saving
|
|
49
|
-
// Wrap in setQueue to properly update queue when Config finishes
|
|
50
|
-
const handleConfigFinished = (config) => {
|
|
51
|
-
setQueue(createConfigExecutionFinishedHandler(addToTimeline, keys)(config));
|
|
52
|
-
};
|
|
53
|
-
const handleConfigAborted = () => {
|
|
54
|
-
setQueue(createConfigExecutionAbortedHandler(addToTimeline)());
|
|
55
|
-
};
|
|
56
|
-
return [
|
|
57
|
-
createConfigDefinitionWithKeys(keys, handleConfigFinished, handleConfigAborted),
|
|
58
|
-
];
|
|
59
|
-
}
|
|
60
|
-
else {
|
|
61
|
-
// Regular execution - just exit for now
|
|
62
|
-
addToTimeline(markAsDone(first));
|
|
63
|
-
exitApp(0);
|
|
64
|
-
return [];
|
|
65
|
-
}
|
|
66
|
-
});
|
|
67
|
-
}
|
|
68
|
-
/**
|
|
69
|
-
* Creates execution cancelled handler
|
|
8
|
+
* Creates all execution handlers
|
|
70
9
|
*/
|
|
71
|
-
export function
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
.
|
|
77
|
-
.
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
10
|
+
export function createExecutionHandlers(ops, taskHandlers) {
|
|
11
|
+
const onConfirmed = (tasks) => {
|
|
12
|
+
ops.setQueue(withQueueHandler(ComponentName.Confirm, (first) => {
|
|
13
|
+
const allIntrospect = tasks.every((task) => task.type === TaskType.Introspect);
|
|
14
|
+
const allAnswer = tasks.every((task) => task.type === TaskType.Answer);
|
|
15
|
+
const allConfig = tasks.every((task) => task.type === TaskType.Config);
|
|
16
|
+
const allExecute = tasks.every((task) => task.type === TaskType.Execute);
|
|
17
|
+
const service = ops.service;
|
|
18
|
+
if (!service) {
|
|
19
|
+
ops.addToTimeline(markAsDone(first), createFeedback(FeedbackType.Failed, 'Service not available'));
|
|
20
|
+
exitApp(1);
|
|
21
|
+
return [];
|
|
22
|
+
}
|
|
23
|
+
if (allIntrospect && tasks.length > 0) {
|
|
24
|
+
ops.addToTimeline(markAsDone(first));
|
|
25
|
+
return [
|
|
26
|
+
createIntrospectDefinition(tasks, service, taskHandlers.introspect.onError, taskHandlers.introspect.onComplete, taskHandlers.introspect.onAborted),
|
|
27
|
+
];
|
|
28
|
+
}
|
|
29
|
+
else if (allAnswer && tasks.length > 0) {
|
|
30
|
+
const question = tasks[0].action;
|
|
31
|
+
ops.addToTimeline(markAsDone(first));
|
|
32
|
+
return [
|
|
33
|
+
createAnswerDefinition(question, service, taskHandlers.answer.onError, taskHandlers.answer.onComplete, taskHandlers.answer.onAborted),
|
|
34
|
+
];
|
|
35
|
+
}
|
|
36
|
+
else if (allConfig && tasks.length > 0) {
|
|
37
|
+
const keys = tasks
|
|
38
|
+
.map((task) => task.params?.key)
|
|
39
|
+
.filter((key) => typeof key === 'string');
|
|
40
|
+
ops.addToTimeline(markAsDone(first));
|
|
41
|
+
const handleConfigFinished = (config) => {
|
|
42
|
+
ops.setQueue(createConfigExecutionFinishedHandler(ops.addToTimeline, keys)(config));
|
|
43
|
+
};
|
|
44
|
+
const handleConfigAborted = () => {
|
|
45
|
+
ops.setQueue(createConfigExecutionAbortedHandler(ops.addToTimeline)());
|
|
46
|
+
};
|
|
47
|
+
return [
|
|
48
|
+
createConfigDefinitionWithKeys(keys, handleConfigFinished, handleConfigAborted),
|
|
49
|
+
];
|
|
50
|
+
}
|
|
51
|
+
else if (allExecute && tasks.length > 0) {
|
|
52
|
+
ops.addToTimeline(markAsDone(first));
|
|
53
|
+
return [
|
|
54
|
+
createExecuteDefinition(tasks, service, taskHandlers.execute.onError, taskHandlers.execute.onComplete, taskHandlers.execute.onAborted),
|
|
55
|
+
];
|
|
56
|
+
}
|
|
57
|
+
else {
|
|
58
|
+
ops.addToTimeline(markAsDone(first), createFeedback(FeedbackType.Failed, 'I can only process one type of task at a time for now.'));
|
|
59
|
+
exitApp(0);
|
|
60
|
+
return [];
|
|
61
|
+
}
|
|
62
|
+
}));
|
|
63
|
+
};
|
|
64
|
+
const onCancelled = (tasks) => {
|
|
65
|
+
ops.setQueue(withQueueHandler(ComponentName.Confirm, (first) => {
|
|
66
|
+
const allIntrospect = tasks.every((task) => task.type === TaskType.Introspect);
|
|
67
|
+
const operation = allIntrospect ? 'introspection' : 'execution';
|
|
68
|
+
ops.addToTimeline(markAsDone(first), createFeedback(FeedbackType.Aborted, getCancellationMessage(operation)));
|
|
69
|
+
return undefined;
|
|
70
|
+
}, true, 0));
|
|
71
|
+
};
|
|
72
|
+
return { onConfirmed, onCancelled };
|
|
88
73
|
}
|
|
@@ -2,27 +2,20 @@ import { ComponentName } from '../types/types.js';
|
|
|
2
2
|
import { createReportDefinition } from '../services/components.js';
|
|
3
3
|
import { createErrorHandler, withQueueHandler } from '../services/queue.js';
|
|
4
4
|
/**
|
|
5
|
-
* Creates introspect
|
|
5
|
+
* Creates all introspect handlers
|
|
6
6
|
*/
|
|
7
|
-
export function
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
return undefined;
|
|
19
|
-
}, true, 0);
|
|
20
|
-
}
|
|
21
|
-
/**
|
|
22
|
-
* Creates introspect aborted handler
|
|
23
|
-
*/
|
|
24
|
-
export function createIntrospectAbortedHandler(handleAborted) {
|
|
25
|
-
return () => {
|
|
7
|
+
export function createIntrospectHandlers(ops, handleAborted) {
|
|
8
|
+
const onError = (error) => {
|
|
9
|
+
ops.setQueue(createErrorHandler(ComponentName.Introspect, ops.addToTimeline)(error));
|
|
10
|
+
};
|
|
11
|
+
const onComplete = (message, capabilities) => {
|
|
12
|
+
ops.setQueue(withQueueHandler(ComponentName.Introspect, () => {
|
|
13
|
+
ops.addToTimeline(createReportDefinition(message, capabilities));
|
|
14
|
+
return undefined;
|
|
15
|
+
}, true, 0));
|
|
16
|
+
};
|
|
17
|
+
const onAborted = () => {
|
|
26
18
|
handleAborted('Introspection');
|
|
27
19
|
};
|
|
20
|
+
return { onError, onComplete, onAborted };
|
|
28
21
|
}
|
package/dist/handlers/plan.js
CHANGED
|
@@ -3,46 +3,41 @@ import { createConfirmDefinition, createFeedback, createPlanDefinition, markAsDo
|
|
|
3
3
|
import { FeedbackMessages, formatErrorMessage, getRefiningMessage, } from '../services/messages.js';
|
|
4
4
|
import { exitApp } from '../services/process.js';
|
|
5
5
|
/**
|
|
6
|
-
* Creates plan
|
|
6
|
+
* Creates all plan handlers
|
|
7
7
|
*/
|
|
8
|
-
export function
|
|
9
|
-
|
|
8
|
+
export function createPlanHandlers(ops, handleAborted, executionHandlers) {
|
|
9
|
+
const onAborted = () => {
|
|
10
10
|
handleAborted('Task selection');
|
|
11
11
|
};
|
|
12
|
-
|
|
13
|
-
/**
|
|
14
|
-
* Creates plan abort handler factory
|
|
15
|
-
*/
|
|
16
|
-
export function createPlanAbortHandlerFactory(handleAborted, handlePlanAborted) {
|
|
17
|
-
return (tasks) => {
|
|
12
|
+
const createAbortHandler = (tasks) => {
|
|
18
13
|
const allIntrospect = tasks.every((task) => task.type === TaskType.Introspect);
|
|
19
14
|
if (allIntrospect) {
|
|
20
15
|
return () => {
|
|
21
16
|
handleAborted('Introspection');
|
|
22
17
|
};
|
|
23
18
|
}
|
|
24
|
-
return
|
|
19
|
+
return onAborted;
|
|
25
20
|
};
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
return async (selectedTasks) => {
|
|
32
|
-
// Mark current plan as done and add refinement to queue
|
|
33
|
-
const refinementDef = createRefinement(getRefiningMessage(), handleRefinementAborted);
|
|
34
|
-
setQueue((currentQueue) => {
|
|
21
|
+
const onSelectionConfirmed = async (selectedTasks) => {
|
|
22
|
+
const refinementDef = createRefinement(getRefiningMessage(), () => {
|
|
23
|
+
handleAborted('Plan refinement');
|
|
24
|
+
});
|
|
25
|
+
ops.setQueue((currentQueue) => {
|
|
35
26
|
if (currentQueue.length === 0)
|
|
36
27
|
return currentQueue;
|
|
37
28
|
const [first] = currentQueue;
|
|
38
29
|
if (first.name === ComponentName.Plan) {
|
|
39
|
-
addToTimeline(markAsDone(first));
|
|
30
|
+
ops.addToTimeline(markAsDone(first));
|
|
40
31
|
}
|
|
41
|
-
// Add refinement to queue so it becomes the active component
|
|
42
32
|
return [refinementDef];
|
|
43
33
|
});
|
|
44
|
-
// Process refined command in background
|
|
45
34
|
try {
|
|
35
|
+
const service = ops.service;
|
|
36
|
+
if (!service) {
|
|
37
|
+
ops.addToTimeline(createFeedback(FeedbackType.Failed, FeedbackMessages.UnexpectedError, 'Service not available'));
|
|
38
|
+
exitApp(1);
|
|
39
|
+
return;
|
|
40
|
+
}
|
|
46
41
|
const refinedCommand = selectedTasks
|
|
47
42
|
.map((task) => {
|
|
48
43
|
const action = task.action.toLowerCase().replace(/,/g, ' -');
|
|
@@ -51,32 +46,34 @@ export function createPlanSelectionConfirmedHandler(addToTimeline, service, hand
|
|
|
51
46
|
})
|
|
52
47
|
.join(', ');
|
|
53
48
|
const result = await service.processWithTool(refinedCommand, 'plan');
|
|
54
|
-
|
|
55
|
-
setQueue((currentQueue) => {
|
|
49
|
+
ops.setQueue((currentQueue) => {
|
|
56
50
|
if (currentQueue.length > 0 &&
|
|
57
51
|
currentQueue[0].id === refinementDef.id) {
|
|
58
|
-
addToTimeline(markAsDone(currentQueue[0]));
|
|
52
|
+
ops.addToTimeline(markAsDone(currentQueue[0]));
|
|
59
53
|
}
|
|
60
54
|
return [];
|
|
61
55
|
});
|
|
62
|
-
|
|
63
|
-
const
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
56
|
+
const planDefinition = createPlanDefinition(result.message, result.tasks, createAbortHandler(result.tasks), undefined);
|
|
57
|
+
const confirmDefinition = createConfirmDefinition(() => {
|
|
58
|
+
executionHandlers.onConfirmed(result.tasks);
|
|
59
|
+
}, () => {
|
|
60
|
+
executionHandlers.onCancelled(result.tasks);
|
|
61
|
+
});
|
|
62
|
+
ops.addToTimeline(planDefinition);
|
|
63
|
+
ops.setQueue([confirmDefinition]);
|
|
67
64
|
}
|
|
68
65
|
catch (error) {
|
|
69
66
|
const errorMessage = formatErrorMessage(error);
|
|
70
|
-
|
|
71
|
-
setQueue((currentQueue) => {
|
|
67
|
+
ops.setQueue((currentQueue) => {
|
|
72
68
|
if (currentQueue.length > 0 &&
|
|
73
69
|
currentQueue[0].id === refinementDef.id) {
|
|
74
|
-
addToTimeline(markAsDone(currentQueue[0]));
|
|
70
|
+
ops.addToTimeline(markAsDone(currentQueue[0]));
|
|
75
71
|
}
|
|
76
72
|
return [];
|
|
77
73
|
});
|
|
78
|
-
addToTimeline(createFeedback(FeedbackType.Failed, FeedbackMessages.UnexpectedError, errorMessage));
|
|
74
|
+
ops.addToTimeline(createFeedback(FeedbackType.Failed, FeedbackMessages.UnexpectedError, errorMessage));
|
|
79
75
|
exitApp(1);
|
|
80
76
|
}
|
|
81
77
|
};
|
|
78
|
+
return { onAborted, createAbortHandler, onSelectionConfirmed };
|
|
82
79
|
}
|
|
@@ -16,7 +16,9 @@ export class AnthropicService {
|
|
|
16
16
|
// Build system prompt with additional context based on tool
|
|
17
17
|
let systemPrompt = instructions;
|
|
18
18
|
// Add skills section for applicable tools
|
|
19
|
-
if (toolName === 'plan' ||
|
|
19
|
+
if (toolName === 'plan' ||
|
|
20
|
+
toolName === 'introspect' ||
|
|
21
|
+
toolName === 'execute') {
|
|
20
22
|
const skills = loadSkills();
|
|
21
23
|
const skillsSection = formatSkillsForPrompt(skills);
|
|
22
24
|
systemPrompt += skillsSection;
|
|
@@ -74,6 +76,29 @@ export class AnthropicService {
|
|
|
74
76
|
const content = toolUseContent;
|
|
75
77
|
// Extract and validate response based on tool type
|
|
76
78
|
const input = content.input;
|
|
79
|
+
// Handle execute tool response
|
|
80
|
+
if (toolName === 'execute') {
|
|
81
|
+
if (!input.message || typeof input.message !== 'string') {
|
|
82
|
+
throw new Error('Invalid tool response: missing or invalid message field');
|
|
83
|
+
}
|
|
84
|
+
if (!input.commands || !Array.isArray(input.commands)) {
|
|
85
|
+
throw new Error('Invalid tool response: missing or invalid commands array');
|
|
86
|
+
}
|
|
87
|
+
// Validate each command has required fields
|
|
88
|
+
input.commands.forEach((cmd, i) => {
|
|
89
|
+
if (!cmd.description || typeof cmd.description !== 'string') {
|
|
90
|
+
throw new Error(`Invalid command at index ${String(i)}: missing or invalid 'description' field`);
|
|
91
|
+
}
|
|
92
|
+
if (!cmd.command || typeof cmd.command !== 'string') {
|
|
93
|
+
throw new Error(`Invalid command at index ${String(i)}: missing or invalid 'command' field`);
|
|
94
|
+
}
|
|
95
|
+
});
|
|
96
|
+
return {
|
|
97
|
+
message: input.message,
|
|
98
|
+
tasks: [],
|
|
99
|
+
commands: input.commands,
|
|
100
|
+
};
|
|
101
|
+
}
|
|
77
102
|
// Handle answer tool response
|
|
78
103
|
if (toolName === 'answer') {
|
|
79
104
|
if (!input.question || typeof input.question !== 'string') {
|
package/dist/services/colors.js
CHANGED
|
@@ -301,3 +301,20 @@ export function createAnswerDisplayDefinition(answer) {
|
|
|
301
301
|
export function isStateless(component) {
|
|
302
302
|
return !('state' in component);
|
|
303
303
|
}
|
|
304
|
+
export function createExecuteDefinition(tasks, service, onError, onComplete, onAborted) {
|
|
305
|
+
return {
|
|
306
|
+
id: randomUUID(),
|
|
307
|
+
name: ComponentName.Execute,
|
|
308
|
+
state: {
|
|
309
|
+
done: false,
|
|
310
|
+
isLoading: true,
|
|
311
|
+
},
|
|
312
|
+
props: {
|
|
313
|
+
tasks,
|
|
314
|
+
service,
|
|
315
|
+
onError,
|
|
316
|
+
onComplete,
|
|
317
|
+
onAborted,
|
|
318
|
+
},
|
|
319
|
+
};
|
|
320
|
+
}
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
export var ExecutionStatus;
|
|
2
|
+
(function (ExecutionStatus) {
|
|
3
|
+
ExecutionStatus["Pending"] = "pending";
|
|
4
|
+
ExecutionStatus["Running"] = "running";
|
|
5
|
+
ExecutionStatus["Success"] = "success";
|
|
6
|
+
ExecutionStatus["Failed"] = "failed";
|
|
7
|
+
})(ExecutionStatus || (ExecutionStatus = {}));
|
|
8
|
+
export var ExecutionResult;
|
|
9
|
+
(function (ExecutionResult) {
|
|
10
|
+
ExecutionResult["Success"] = "success";
|
|
11
|
+
ExecutionResult["Error"] = "error";
|
|
12
|
+
ExecutionResult["Aborted"] = "aborted";
|
|
13
|
+
})(ExecutionResult || (ExecutionResult = {}));
|
|
14
|
+
const DEFAULT_DELAY_GENERATOR = (index) => (Math.pow(3, index + 1) * Math.max(Math.random(), Math.random()) + 1) * 1000;
|
|
15
|
+
/**
|
|
16
|
+
* Dummy executor that simulates command execution with configurable delays.
|
|
17
|
+
* Supports mocked responses for testing different scenarios.
|
|
18
|
+
*/
|
|
19
|
+
export class DummyExecutor {
|
|
20
|
+
mockedResponses = new Map();
|
|
21
|
+
delayGenerator;
|
|
22
|
+
constructor(delayGenerator = DEFAULT_DELAY_GENERATOR) {
|
|
23
|
+
this.delayGenerator = delayGenerator;
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Set a mocked response for a specific command
|
|
27
|
+
*/
|
|
28
|
+
mock(command, response) {
|
|
29
|
+
this.mockedResponses.set(command, response);
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Clear all mocked responses
|
|
33
|
+
*/
|
|
34
|
+
clearMocks() {
|
|
35
|
+
this.mockedResponses.clear();
|
|
36
|
+
}
|
|
37
|
+
execute(cmd, onProgress, index = 0) {
|
|
38
|
+
return new Promise((resolve) => {
|
|
39
|
+
onProgress?.(ExecutionStatus.Running);
|
|
40
|
+
const delay = this.delayGenerator(index);
|
|
41
|
+
setTimeout(() => {
|
|
42
|
+
const mocked = this.mockedResponses.get(cmd.command);
|
|
43
|
+
const commandResult = {
|
|
44
|
+
description: cmd.description,
|
|
45
|
+
command: cmd.command,
|
|
46
|
+
output: mocked?.output ?? '',
|
|
47
|
+
errors: mocked?.errors ?? '',
|
|
48
|
+
result: mocked?.result ?? ExecutionResult.Success,
|
|
49
|
+
error: mocked?.error,
|
|
50
|
+
};
|
|
51
|
+
onProgress?.(commandResult.result === ExecutionResult.Success
|
|
52
|
+
? ExecutionStatus.Success
|
|
53
|
+
: ExecutionStatus.Failed);
|
|
54
|
+
resolve(commandResult);
|
|
55
|
+
}, delay);
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Default executor uses DummyExecutor for development and testing.
|
|
61
|
+
* To implement real shell execution, create a RealExecutor class that:
|
|
62
|
+
* - Spawns process with cmd.command in shell mode using child_process.spawn()
|
|
63
|
+
* - Sets working directory from cmd.workdir
|
|
64
|
+
* - Handles cmd.timeout for command timeout
|
|
65
|
+
* - Captures stdout and stderr streams
|
|
66
|
+
* - Calls onProgress with Running/Success/Failed status
|
|
67
|
+
* - Returns CommandOutput with actual stdout, stderr, exitCode
|
|
68
|
+
* - Handles errors (spawn failures, timeouts, non-zero exit codes)
|
|
69
|
+
*/
|
|
70
|
+
const executor = new DummyExecutor();
|
|
71
|
+
/**
|
|
72
|
+
* Execute a single shell command
|
|
73
|
+
*/
|
|
74
|
+
export function executeCommand(cmd, onProgress, index = 0) {
|
|
75
|
+
return executor.execute(cmd, onProgress, index);
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* Execute multiple commands sequentially
|
|
79
|
+
*/
|
|
80
|
+
export async function executeCommands(commands, onProgress) {
|
|
81
|
+
const results = [];
|
|
82
|
+
for (let i = 0; i < commands.length; i++) {
|
|
83
|
+
const cmd = commands[i];
|
|
84
|
+
onProgress?.({
|
|
85
|
+
currentIndex: i,
|
|
86
|
+
total: commands.length,
|
|
87
|
+
command: cmd,
|
|
88
|
+
status: ExecutionStatus.Running,
|
|
89
|
+
});
|
|
90
|
+
const output = await executeCommand(cmd, (status) => {
|
|
91
|
+
onProgress?.({
|
|
92
|
+
currentIndex: i,
|
|
93
|
+
total: commands.length,
|
|
94
|
+
command: cmd,
|
|
95
|
+
status,
|
|
96
|
+
output: status !== ExecutionStatus.Running ? results[i] : undefined,
|
|
97
|
+
});
|
|
98
|
+
}, i);
|
|
99
|
+
results.push(output);
|
|
100
|
+
// Update with final status
|
|
101
|
+
onProgress?.({
|
|
102
|
+
currentIndex: i,
|
|
103
|
+
total: commands.length,
|
|
104
|
+
command: cmd,
|
|
105
|
+
status: output.result === ExecutionResult.Success
|
|
106
|
+
? ExecutionStatus.Success
|
|
107
|
+
: ExecutionStatus.Failed,
|
|
108
|
+
output,
|
|
109
|
+
});
|
|
110
|
+
// Stop if critical command failed
|
|
111
|
+
const isCritical = cmd.critical !== false;
|
|
112
|
+
if (output.result !== ExecutionResult.Success && isCritical) {
|
|
113
|
+
break;
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
return results;
|
|
117
|
+
}
|
|
@@ -35,6 +35,7 @@ export const toolRegistry = new ToolRegistry();
|
|
|
35
35
|
// Register built-in tools
|
|
36
36
|
import { answerTool } from '../tools/answer.tool.js';
|
|
37
37
|
import { configTool } from '../tools/config.tool.js';
|
|
38
|
+
import { executeTool } from '../tools/execute.tool.js';
|
|
38
39
|
import { introspectTool } from '../tools/introspect.tool.js';
|
|
39
40
|
import { planTool } from '../tools/plan.tool.js';
|
|
40
41
|
toolRegistry.register('plan', {
|
|
@@ -53,3 +54,7 @@ toolRegistry.register('config', {
|
|
|
53
54
|
schema: configTool,
|
|
54
55
|
instructionsPath: 'config/CONFIG.md',
|
|
55
56
|
});
|
|
57
|
+
toolRegistry.register('execute', {
|
|
58
|
+
schema: executeTool,
|
|
59
|
+
instructionsPath: 'config/EXECUTE.md',
|
|
60
|
+
});
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Formats a duration in milliseconds to a human-readable string.
|
|
3
|
+
* Uses correct singular/plural forms.
|
|
4
|
+
*/
|
|
5
|
+
export function formatDuration(ms) {
|
|
6
|
+
const totalSeconds = Math.floor(ms / 1000);
|
|
7
|
+
const hours = Math.floor(totalSeconds / 3600);
|
|
8
|
+
const minutes = Math.floor((totalSeconds % 3600) / 60);
|
|
9
|
+
const seconds = totalSeconds % 60;
|
|
10
|
+
const parts = [];
|
|
11
|
+
if (hours > 0) {
|
|
12
|
+
parts.push(`${String(hours)} ${hours === 1 ? 'hour' : 'hours'}`);
|
|
13
|
+
}
|
|
14
|
+
if (minutes > 0) {
|
|
15
|
+
parts.push(`${String(minutes)} ${minutes === 1 ? 'minute' : 'minutes'}`);
|
|
16
|
+
}
|
|
17
|
+
if (seconds > 0 || parts.length === 0) {
|
|
18
|
+
parts.push(`${String(seconds)} ${seconds === 1 ? 'second' : 'seconds'}`);
|
|
19
|
+
}
|
|
20
|
+
return parts.join(' ');
|
|
21
|
+
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
export const executeTool = {
|
|
2
|
+
name: 'execute',
|
|
3
|
+
description: 'Execute shell commands from planned tasks. Translates task descriptions into specific shell commands that can be run in the terminal. Called after PLAN has created execute tasks and user has confirmed.',
|
|
4
|
+
input_schema: {
|
|
5
|
+
type: 'object',
|
|
6
|
+
properties: {
|
|
7
|
+
message: {
|
|
8
|
+
type: 'string',
|
|
9
|
+
description: 'Brief status message about the execution. Must be a single sentence, maximum 64 characters, ending with a period.',
|
|
10
|
+
},
|
|
11
|
+
commands: {
|
|
12
|
+
type: 'array',
|
|
13
|
+
description: 'Array of commands to execute sequentially',
|
|
14
|
+
items: {
|
|
15
|
+
type: 'object',
|
|
16
|
+
properties: {
|
|
17
|
+
description: {
|
|
18
|
+
type: 'string',
|
|
19
|
+
description: 'Brief description of what this command does. Maximum 64 characters.',
|
|
20
|
+
},
|
|
21
|
+
command: {
|
|
22
|
+
type: 'string',
|
|
23
|
+
description: 'The exact shell command to run. Must be a valid shell command.',
|
|
24
|
+
},
|
|
25
|
+
workdir: {
|
|
26
|
+
type: 'string',
|
|
27
|
+
description: 'Optional working directory for the command. Defaults to current directory if not specified.',
|
|
28
|
+
},
|
|
29
|
+
timeout: {
|
|
30
|
+
type: 'number',
|
|
31
|
+
description: 'Optional timeout in milliseconds. Defaults to 30000 (30 seconds).',
|
|
32
|
+
},
|
|
33
|
+
critical: {
|
|
34
|
+
type: 'boolean',
|
|
35
|
+
description: 'Whether failure should stop execution of subsequent commands. Defaults to true.',
|
|
36
|
+
},
|
|
37
|
+
},
|
|
38
|
+
required: ['description', 'command'],
|
|
39
|
+
},
|
|
40
|
+
},
|
|
41
|
+
},
|
|
42
|
+
required: ['message', 'commands'],
|
|
43
|
+
},
|
|
44
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/dist/types/types.js
CHANGED
|
@@ -12,6 +12,7 @@ export var ComponentName;
|
|
|
12
12
|
ComponentName["Report"] = "report";
|
|
13
13
|
ComponentName["Answer"] = "answer";
|
|
14
14
|
ComponentName["AnswerDisplay"] = "answerDisplay";
|
|
15
|
+
ComponentName["Execute"] = "execute";
|
|
15
16
|
})(ComponentName || (ComponentName = {}));
|
|
16
17
|
export var TaskType;
|
|
17
18
|
(function (TaskType) {
|
package/dist/ui/Component.js
CHANGED
|
@@ -6,6 +6,7 @@ import { AnswerDisplay } from './AnswerDisplay.js';
|
|
|
6
6
|
import { Command } from './Command.js';
|
|
7
7
|
import { Confirm } from './Confirm.js';
|
|
8
8
|
import { Config } from './Config.js';
|
|
9
|
+
import { Execute } from './Execute.js';
|
|
9
10
|
import { Feedback } from './Feedback.js';
|
|
10
11
|
import { Introspect } from './Introspect.js';
|
|
11
12
|
import { Message } from './Message.js';
|
|
@@ -60,5 +61,10 @@ export const Component = React.memo(function Component({ def, debug, }) {
|
|
|
60
61
|
}
|
|
61
62
|
case ComponentName.AnswerDisplay:
|
|
62
63
|
return _jsx(AnswerDisplay, { ...def.props });
|
|
64
|
+
case ComponentName.Execute: {
|
|
65
|
+
const props = def.props;
|
|
66
|
+
const state = def.state;
|
|
67
|
+
return _jsx(Execute, { ...props, state: state });
|
|
68
|
+
}
|
|
63
69
|
}
|
|
64
70
|
});
|
package/dist/ui/Confirm.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
import React from 'react';
|
|
3
3
|
import { Box, Text } from 'ink';
|
|
4
|
-
import { Colors } from '../services/colors.js';
|
|
4
|
+
import { Colors, Palette } from '../services/colors.js';
|
|
5
5
|
import { useInput } from '../services/keyboard.js';
|
|
6
6
|
export function Confirm({ message, state, onConfirmed, onCancelled, }) {
|
|
7
7
|
const done = state?.done ?? false;
|
|
@@ -30,7 +30,7 @@ export function Confirm({ message, state, onConfirmed, onCancelled, }) {
|
|
|
30
30
|
}
|
|
31
31
|
}, { isActive: !done });
|
|
32
32
|
const options = [
|
|
33
|
-
{ label: 'yes', value: 'yes', color:
|
|
33
|
+
{ label: 'yes', value: 'yes', color: Palette.BrightGreen },
|
|
34
34
|
{ label: 'no', value: 'no', color: Colors.Status.Error },
|
|
35
35
|
];
|
|
36
36
|
if (done) {
|