prompt-language-shell 0.4.2 → 0.4.4
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/ANSWER.md +119 -0
- package/dist/config/PLAN.md +26 -5
- package/dist/handlers/answer.js +28 -0
- package/dist/handlers/command.js +38 -0
- package/dist/handlers/config.js +39 -0
- package/dist/handlers/execution.js +68 -0
- package/dist/handlers/introspect.js +28 -0
- package/dist/handlers/plan.js +82 -0
- package/dist/services/anthropic.js +18 -2
- package/dist/{types → services}/colors.js +58 -6
- package/dist/services/components.js +27 -1
- package/dist/services/queue.js +52 -0
- package/dist/services/tool-registry.js +5 -0
- package/dist/tools/answer.tool.js +18 -0
- package/dist/types/types.js +2 -0
- package/dist/ui/Answer.js +71 -0
- package/dist/ui/AnswerDisplay.js +8 -0
- package/dist/ui/Command.js +3 -1
- package/dist/ui/Component.js +14 -2
- package/dist/ui/Config.js +1 -1
- package/dist/ui/Confirm.js +4 -3
- package/dist/ui/Feedback.js +2 -2
- package/dist/ui/Introspect.js +3 -1
- package/dist/ui/Label.js +4 -2
- package/dist/ui/List.js +3 -3
- package/dist/ui/Main.js +35 -232
- package/dist/ui/Plan.js +13 -10
- package/dist/ui/Separator.js +1 -1
- package/package.json +1 -1
- /package/dist/services/{config.js → configuration.js} +0 -0
|
@@ -33,6 +33,7 @@ class ToolRegistry {
|
|
|
33
33
|
// Create singleton instance
|
|
34
34
|
export const toolRegistry = new ToolRegistry();
|
|
35
35
|
// Register built-in tools
|
|
36
|
+
import { answerTool } from '../tools/answer.tool.js';
|
|
36
37
|
import { introspectTool } from '../tools/introspect.tool.js';
|
|
37
38
|
import { planTool } from '../tools/plan.tool.js';
|
|
38
39
|
toolRegistry.register('plan', {
|
|
@@ -43,3 +44,7 @@ toolRegistry.register('introspect', {
|
|
|
43
44
|
schema: introspectTool,
|
|
44
45
|
instructionsPath: 'config/INTROSPECT.md',
|
|
45
46
|
});
|
|
47
|
+
toolRegistry.register('answer', {
|
|
48
|
+
schema: answerTool,
|
|
49
|
+
instructionsPath: 'config/ANSWER.md',
|
|
50
|
+
});
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
export const answerTool = {
|
|
2
|
+
name: 'answer',
|
|
3
|
+
description: 'Answer questions and provide up-to-date information using web search. Called after PLAN has identified an answer request and user has confirmed. Searches the web for current data and provides concise, helpful responses formatted for terminal display.',
|
|
4
|
+
input_schema: {
|
|
5
|
+
type: 'object',
|
|
6
|
+
properties: {
|
|
7
|
+
question: {
|
|
8
|
+
type: 'string',
|
|
9
|
+
description: 'The question or information request from the user. Should be a clear, complete question.',
|
|
10
|
+
},
|
|
11
|
+
answer: {
|
|
12
|
+
type: 'string',
|
|
13
|
+
description: 'The answer to the question. Must be concise and well-formatted. Maximum 4 lines of text, each line maximum 80 characters. Use natural line breaks for readability.',
|
|
14
|
+
},
|
|
15
|
+
},
|
|
16
|
+
required: ['question', 'answer'],
|
|
17
|
+
},
|
|
18
|
+
};
|
package/dist/types/types.js
CHANGED
|
@@ -10,6 +10,8 @@ export var ComponentName;
|
|
|
10
10
|
ComponentName["Confirm"] = "confirm";
|
|
11
11
|
ComponentName["Introspect"] = "introspect";
|
|
12
12
|
ComponentName["Report"] = "report";
|
|
13
|
+
ComponentName["Answer"] = "answer";
|
|
14
|
+
ComponentName["AnswerDisplay"] = "answerDisplay";
|
|
13
15
|
})(ComponentName || (ComponentName = {}));
|
|
14
16
|
export var TaskType;
|
|
15
17
|
(function (TaskType) {
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { useEffect, useState } from 'react';
|
|
3
|
+
import { Box, Text, useInput } from 'ink';
|
|
4
|
+
import { getTextColor } from '../services/colors.js';
|
|
5
|
+
import { Spinner } from './Spinner.js';
|
|
6
|
+
const MIN_PROCESSING_TIME = 1000;
|
|
7
|
+
export function Answer({ question, state, service, onError, onComplete, onAborted, }) {
|
|
8
|
+
const done = state?.done ?? false;
|
|
9
|
+
const isCurrent = done === false;
|
|
10
|
+
const [error, setError] = useState(null);
|
|
11
|
+
const [isLoading, setIsLoading] = useState(state?.isLoading ?? !done);
|
|
12
|
+
useInput((input, key) => {
|
|
13
|
+
if (key.escape && isLoading && !done) {
|
|
14
|
+
setIsLoading(false);
|
|
15
|
+
onAborted();
|
|
16
|
+
}
|
|
17
|
+
}, { isActive: isLoading && !done });
|
|
18
|
+
useEffect(() => {
|
|
19
|
+
// Skip processing if done
|
|
20
|
+
if (done) {
|
|
21
|
+
return;
|
|
22
|
+
}
|
|
23
|
+
// Skip processing if no service available
|
|
24
|
+
if (!service) {
|
|
25
|
+
setError('No service available');
|
|
26
|
+
setIsLoading(false);
|
|
27
|
+
return;
|
|
28
|
+
}
|
|
29
|
+
let mounted = true;
|
|
30
|
+
async function process(svc) {
|
|
31
|
+
const startTime = Date.now();
|
|
32
|
+
try {
|
|
33
|
+
// Call answer tool
|
|
34
|
+
const result = await svc.processWithTool(question, 'answer');
|
|
35
|
+
const elapsed = Date.now() - startTime;
|
|
36
|
+
const remainingTime = Math.max(0, MIN_PROCESSING_TIME - elapsed);
|
|
37
|
+
await new Promise((resolve) => setTimeout(resolve, remainingTime));
|
|
38
|
+
if (mounted) {
|
|
39
|
+
// Extract answer from result
|
|
40
|
+
const answer = result.answer || '';
|
|
41
|
+
setIsLoading(false);
|
|
42
|
+
onComplete?.(answer);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
catch (err) {
|
|
46
|
+
const elapsed = Date.now() - startTime;
|
|
47
|
+
const remainingTime = Math.max(0, MIN_PROCESSING_TIME - elapsed);
|
|
48
|
+
await new Promise((resolve) => setTimeout(resolve, remainingTime));
|
|
49
|
+
if (mounted) {
|
|
50
|
+
const errorMessage = err instanceof Error ? err.message : 'Unknown error occurred';
|
|
51
|
+
setIsLoading(false);
|
|
52
|
+
if (onError) {
|
|
53
|
+
onError(errorMessage);
|
|
54
|
+
}
|
|
55
|
+
else {
|
|
56
|
+
setError(errorMessage);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
process(service);
|
|
62
|
+
return () => {
|
|
63
|
+
mounted = false;
|
|
64
|
+
};
|
|
65
|
+
}, [question, done, service, onComplete, onError]);
|
|
66
|
+
// Return null when done (like Introspect)
|
|
67
|
+
if (done || (!isLoading && !error)) {
|
|
68
|
+
return null;
|
|
69
|
+
}
|
|
70
|
+
return (_jsxs(Box, { alignSelf: "flex-start", flexDirection: "column", children: [isLoading && (_jsxs(Box, { children: [_jsx(Text, { color: getTextColor(isCurrent), children: "Finding answer. " }), _jsx(Spinner, {})] })), error && (_jsx(Box, { marginTop: 1, children: _jsxs(Text, { color: "red", children: ["Error: ", error] }) }))] }));
|
|
71
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { Box, Text } from 'ink';
|
|
3
|
+
import { Colors } from '../services/colors.js';
|
|
4
|
+
export function AnswerDisplay({ answer }) {
|
|
5
|
+
// Split answer into lines and display with indentation
|
|
6
|
+
const lines = answer.split('\n');
|
|
7
|
+
return (_jsx(Box, { flexDirection: "column", paddingLeft: 2, children: lines.map((line, index) => (_jsx(Text, { color: Colors.Text.Active, children: line }, index))) }));
|
|
8
|
+
}
|
package/dist/ui/Command.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { jsxs as _jsxs, jsx as _jsx, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
2
|
import { useEffect, useState } from 'react';
|
|
3
3
|
import { Box, Text, useInput } from 'ink';
|
|
4
|
+
import { getTextColor } from '../services/colors.js';
|
|
4
5
|
import { Spinner } from './Spinner.js';
|
|
5
6
|
const MIN_PROCESSING_TIME = 1000; // purely for visual effect
|
|
6
7
|
export function Command({ command, state, service, children, onError, onComplete, onAborted, }) {
|
|
@@ -58,5 +59,6 @@ export function Command({ command, state, service, children, onError, onComplete
|
|
|
58
59
|
mounted = false;
|
|
59
60
|
};
|
|
60
61
|
}, [command, done, service]);
|
|
61
|
-
|
|
62
|
+
const isCurrent = done === false;
|
|
63
|
+
return (_jsxs(Box, { alignSelf: "flex-start", flexDirection: "column", children: [_jsxs(Box, { children: [_jsxs(Text, { color: getTextColor(isCurrent), children: ["> pls ", command] }), isLoading && (_jsxs(_Fragment, { children: [_jsx(Text, { children: " " }), _jsx(Spinner, {})] }))] }), error && (_jsx(Box, { marginTop: 1, children: _jsxs(Text, { color: "red", children: ["Error: ", error] }) })), children] }));
|
|
62
64
|
}
|
package/dist/ui/Component.js
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
2
|
import React from 'react';
|
|
3
3
|
import { ComponentName } from '../types/types.js';
|
|
4
|
+
import { Answer } from './Answer.js';
|
|
5
|
+
import { AnswerDisplay } from './AnswerDisplay.js';
|
|
4
6
|
import { Command } from './Command.js';
|
|
5
7
|
import { Confirm } from './Confirm.js';
|
|
6
8
|
import { Config } from './Config.js';
|
|
@@ -25,8 +27,11 @@ export const Component = React.memo(function Component({ def, debug, }) {
|
|
|
25
27
|
const state = def.state;
|
|
26
28
|
return _jsx(Command, { ...props, state: state });
|
|
27
29
|
}
|
|
28
|
-
case ComponentName.Plan:
|
|
29
|
-
|
|
30
|
+
case ComponentName.Plan: {
|
|
31
|
+
const props = def.props;
|
|
32
|
+
const state = def.state;
|
|
33
|
+
return _jsx(Plan, { ...props, state: state, debug: debug });
|
|
34
|
+
}
|
|
30
35
|
case ComponentName.Feedback:
|
|
31
36
|
return _jsx(Feedback, { ...def.props });
|
|
32
37
|
case ComponentName.Message:
|
|
@@ -48,5 +53,12 @@ export const Component = React.memo(function Component({ def, debug, }) {
|
|
|
48
53
|
}
|
|
49
54
|
case ComponentName.Report:
|
|
50
55
|
return _jsx(Report, { ...def.props });
|
|
56
|
+
case ComponentName.Answer: {
|
|
57
|
+
const props = def.props;
|
|
58
|
+
const state = def.state;
|
|
59
|
+
return _jsx(Answer, { ...props, state: state });
|
|
60
|
+
}
|
|
61
|
+
case ComponentName.AnswerDisplay:
|
|
62
|
+
return _jsx(AnswerDisplay, { ...def.props });
|
|
51
63
|
}
|
|
52
64
|
});
|
package/dist/ui/Config.js
CHANGED
|
@@ -2,7 +2,7 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
|
2
2
|
import React from 'react';
|
|
3
3
|
import { Box, Text, useFocus, useInput } from 'ink';
|
|
4
4
|
import TextInput from 'ink-text-input';
|
|
5
|
-
import { Colors } from '../
|
|
5
|
+
import { Colors } from '../services/colors.js';
|
|
6
6
|
export var StepType;
|
|
7
7
|
(function (StepType) {
|
|
8
8
|
StepType["Text"] = "text";
|
package/dist/ui/Confirm.js
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
import React from 'react';
|
|
3
3
|
import { Box, Text, useInput } from 'ink';
|
|
4
|
-
import { Colors } from '../
|
|
4
|
+
import { Colors } from '../services/colors.js';
|
|
5
5
|
export function Confirm({ message, state, onConfirmed, onCancelled, }) {
|
|
6
6
|
const done = state?.done ?? false;
|
|
7
|
+
const isCurrent = done === false;
|
|
7
8
|
const [selectedIndex, setSelectedIndex] = React.useState(0); // 0 = Yes, 1 = No
|
|
8
9
|
useInput((input, key) => {
|
|
9
10
|
if (done)
|
|
@@ -33,9 +34,9 @@ export function Confirm({ message, state, onConfirmed, onCancelled, }) {
|
|
|
33
34
|
];
|
|
34
35
|
if (done) {
|
|
35
36
|
// When done, show both the message and user's choice in timeline
|
|
36
|
-
return (_jsxs(Box, { flexDirection: "column", children: [_jsx(Box, { marginBottom: 1, children: _jsx(Text, { children: message }) }), _jsx(Box, { children: _jsxs(Text, { color:
|
|
37
|
+
return (_jsxs(Box, { flexDirection: "column", children: [_jsx(Box, { marginBottom: 1, children: _jsx(Text, { color: undefined, children: message }) }), _jsx(Box, { children: _jsxs(Text, { color: Colors.Text.Inactive, children: ["> ", options[selectedIndex].label] }) })] }));
|
|
37
38
|
}
|
|
38
|
-
return (_jsxs(Box, { flexDirection: "column", children: [_jsx(Box, { marginBottom: 1, children: _jsx(Text, { children: message }) }), _jsxs(Box, { children: [_jsx(Text, { color: Colors.Action.Select, children: ">" }), _jsx(Text, { children: " " }), _jsx(Box, { children: options.map((option, index) => {
|
|
39
|
+
return (_jsxs(Box, { flexDirection: "column", children: [_jsx(Box, { marginBottom: 1, children: _jsx(Text, { color: isCurrent ? Colors.Text.Active : Colors.Text.Inactive, children: message }) }), _jsxs(Box, { children: [_jsx(Text, { color: Colors.Action.Select, children: ">" }), _jsx(Text, { children: " " }), _jsx(Box, { children: options.map((option, index) => {
|
|
39
40
|
const isSelected = index === selectedIndex;
|
|
40
41
|
return (_jsx(Box, { marginRight: 2, children: _jsx(Text, { color: isSelected ? option.color : undefined, dimColor: !isSelected, bold: isSelected, children: option.label }) }, option.value));
|
|
41
42
|
}) })] })] }));
|
package/dist/ui/Feedback.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { jsxs as _jsxs, jsx as _jsx } from "react/jsx-runtime";
|
|
2
2
|
import { Box, Text } from 'ink';
|
|
3
|
-
import {
|
|
3
|
+
import { getFeedbackColor } from '../services/colors.js';
|
|
4
4
|
import { FeedbackType } from '../types/types.js';
|
|
5
5
|
function getSymbol(type) {
|
|
6
6
|
return {
|
|
@@ -11,7 +11,7 @@ function getSymbol(type) {
|
|
|
11
11
|
}[type];
|
|
12
12
|
}
|
|
13
13
|
export function Feedback({ type, message }) {
|
|
14
|
-
const color =
|
|
14
|
+
const color = getFeedbackColor(type, false);
|
|
15
15
|
const symbol = getSymbol(type);
|
|
16
16
|
return (_jsx(Box, { children: _jsxs(Text, { color: color, children: [symbol, " ", message] }) }));
|
|
17
17
|
}
|
package/dist/ui/Introspect.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
import { useEffect, useState } from 'react';
|
|
3
3
|
import { Box, Text, useInput } from 'ink';
|
|
4
|
+
import { getTextColor } from '../services/colors.js';
|
|
4
5
|
import { Spinner } from './Spinner.js';
|
|
5
6
|
const MIN_PROCESSING_TIME = 1000;
|
|
6
7
|
const BUILT_IN_CAPABILITIES = new Set([
|
|
@@ -32,6 +33,7 @@ function parseCapabilityFromTask(task) {
|
|
|
32
33
|
}
|
|
33
34
|
export function Introspect({ tasks, state, service, children, onError, onComplete, onAborted, }) {
|
|
34
35
|
const done = state?.done ?? false;
|
|
36
|
+
const isCurrent = done === false;
|
|
35
37
|
const [error, setError] = useState(null);
|
|
36
38
|
const [isLoading, setIsLoading] = useState(state?.isLoading ?? !done);
|
|
37
39
|
useInput((input, key) => {
|
|
@@ -94,5 +96,5 @@ export function Introspect({ tasks, state, service, children, onError, onComplet
|
|
|
94
96
|
if (!isLoading && !error && !children) {
|
|
95
97
|
return null;
|
|
96
98
|
}
|
|
97
|
-
return (_jsxs(Box, { alignSelf: "flex-start", flexDirection: "column", children: [isLoading && (_jsxs(Box, { children: [_jsx(Text, { children: "Listing capabilities. " }), _jsx(Spinner, {})] })), error && (_jsx(Box, { marginTop: 1, children: _jsxs(Text, { color: "red", children: ["Error: ", error] }) })), children] }));
|
|
99
|
+
return (_jsxs(Box, { alignSelf: "flex-start", flexDirection: "column", children: [isLoading && (_jsxs(Box, { children: [_jsx(Text, { color: getTextColor(isCurrent), children: "Listing capabilities. " }), _jsx(Spinner, {})] })), error && (_jsx(Box, { marginTop: 1, children: _jsxs(Text, { color: "red", children: ["Error: ", error] }) })), children] }));
|
|
98
100
|
}
|
package/dist/ui/Label.js
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
import { Box, Text } from 'ink';
|
|
3
|
+
import { getTaskColors } from '../services/colors.js';
|
|
3
4
|
import { Separator } from './Separator.js';
|
|
4
|
-
export function Label({ description,
|
|
5
|
-
|
|
5
|
+
export function Label({ description, taskType, showType = false, isCurrent = false, }) {
|
|
6
|
+
const colors = getTaskColors(taskType, isCurrent);
|
|
7
|
+
return (_jsxs(Box, { children: [_jsx(Text, { color: colors.description, children: description }), showType && (_jsxs(_Fragment, { children: [_jsx(Separator, {}), _jsx(Text, { color: colors.type, children: taskType })] }))] }));
|
|
6
8
|
}
|
package/dist/ui/List.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
1
|
+
import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
import { Box, Text } from 'ink';
|
|
3
|
-
import {
|
|
3
|
+
import { Separator } from './Separator.js';
|
|
4
4
|
export const List = ({ items, level = 0, highlightedIndex = null, highlightedParentIndex = null, showType = false, }) => {
|
|
5
5
|
const marginLeft = level > 0 ? 4 : 0;
|
|
6
6
|
return (_jsx(Box, { flexDirection: "column", marginLeft: marginLeft, children: items.map((item, index) => {
|
|
@@ -21,6 +21,6 @@ export const List = ({ items, level = 0, highlightedIndex = null, highlightedPar
|
|
|
21
21
|
(isHighlighted && item.type.highlightedColor
|
|
22
22
|
? item.type.highlightedColor
|
|
23
23
|
: 'whiteBright');
|
|
24
|
-
return (_jsxs(Box, { flexDirection: "column", children: [_jsxs(Box, { children: [_jsx(Text, { color: markerColor, children: marker }), _jsx(
|
|
24
|
+
return (_jsxs(Box, { flexDirection: "column", children: [_jsxs(Box, { children: [_jsx(Text, { color: markerColor, children: marker }), _jsx(Text, { color: descriptionColor, children: item.description.text }), showType && (_jsxs(_Fragment, { children: [_jsx(Separator, {}), _jsx(Text, { color: typeColor, children: item.type.text })] }))] }), item.children && item.children.length > 0 && (_jsx(List, { items: item.children, level: level + 1, highlightedIndex: shouldHighlightChildren ? highlightedIndex : null, showType: showType }))] }, index));
|
|
25
25
|
}) }));
|
|
26
26
|
};
|
package/dist/ui/Main.js
CHANGED
|
@@ -1,12 +1,18 @@
|
|
|
1
1
|
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
2
|
import React from 'react';
|
|
3
3
|
import { useInput } from 'ink';
|
|
4
|
-
import {
|
|
4
|
+
import { FeedbackType } from '../types/types.js';
|
|
5
5
|
import { createAnthropicService, } from '../services/anthropic.js';
|
|
6
|
-
import { getConfigurationRequiredMessage, hasValidAnthropicKey, loadConfig, loadDebugSetting,
|
|
7
|
-
import {
|
|
8
|
-
import { createCommandDefinition,
|
|
6
|
+
import { getConfigurationRequiredMessage, hasValidAnthropicKey, loadConfig, loadDebugSetting, saveDebugSetting, } from '../services/configuration.js';
|
|
7
|
+
import { getCancellationMessage } from '../services/messages.js';
|
|
8
|
+
import { createCommandDefinition, createConfigDefinition, createFeedback, createMessage, createWelcomeDefinition, isStateless, markAsDone, } from '../services/components.js';
|
|
9
9
|
import { exitApp } from '../services/process.js';
|
|
10
|
+
import { createAnswerAbortedHandler, createAnswerCompleteHandler, createAnswerErrorHandler, } from '../handlers/answer.js';
|
|
11
|
+
import { createCommandAbortedHandler, createCommandCompleteHandler, createCommandErrorHandler, } from '../handlers/command.js';
|
|
12
|
+
import { createConfigAbortedHandler, createConfigFinishedHandler, } from '../handlers/config.js';
|
|
13
|
+
import { createExecutionCancelledHandler, createExecutionConfirmedHandler, } from '../handlers/execution.js';
|
|
14
|
+
import { createIntrospectAbortedHandler, createIntrospectCompleteHandler, createIntrospectErrorHandler, } from '../handlers/introspect.js';
|
|
15
|
+
import { createPlanAbortedHandler, createPlanAbortHandlerFactory, createPlanSelectionConfirmedHandler, } from '../handlers/plan.js';
|
|
10
16
|
import { Column } from './Column.js';
|
|
11
17
|
export const Main = ({ app, command }) => {
|
|
12
18
|
// Initialize service from existing config if available
|
|
@@ -52,18 +58,7 @@ export const Main = ({ app, command }) => {
|
|
|
52
58
|
return currentQueue;
|
|
53
59
|
});
|
|
54
60
|
}, [addToTimeline]);
|
|
55
|
-
const handleCommandError = React.useCallback((error) =>
|
|
56
|
-
setQueue((currentQueue) => {
|
|
57
|
-
if (currentQueue.length === 0)
|
|
58
|
-
return currentQueue;
|
|
59
|
-
const [first] = currentQueue;
|
|
60
|
-
if (first.name === ComponentName.Command) {
|
|
61
|
-
addToTimeline(markAsDone(first), createFeedback(FeedbackType.Failed, FeedbackMessages.UnexpectedError, error));
|
|
62
|
-
}
|
|
63
|
-
exitApp(1);
|
|
64
|
-
return [];
|
|
65
|
-
});
|
|
66
|
-
}, [addToTimeline]);
|
|
61
|
+
const handleCommandError = React.useCallback((error) => setQueue(createCommandErrorHandler(addToTimeline)(error)), [addToTimeline]);
|
|
67
62
|
const handleAborted = React.useCallback((operationName) => {
|
|
68
63
|
setQueue((currentQueue) => {
|
|
69
64
|
if (currentQueue.length === 0)
|
|
@@ -76,181 +71,31 @@ export const Main = ({ app, command }) => {
|
|
|
76
71
|
return [];
|
|
77
72
|
});
|
|
78
73
|
}, [addToTimeline]);
|
|
79
|
-
const handleConfigAborted = React.useCallback(()
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
const
|
|
83
|
-
handleAborted('Task selection');
|
|
84
|
-
}, [handleAborted]);
|
|
85
|
-
const createPlanAbortHandler = React.useCallback((tasks) => {
|
|
86
|
-
const allIntrospect = tasks.every((task) => task.type === TaskType.Introspect);
|
|
87
|
-
if (allIntrospect) {
|
|
88
|
-
return () => handleAborted('Introspection');
|
|
89
|
-
}
|
|
90
|
-
return handlePlanAborted;
|
|
91
|
-
}, [handleAborted, handlePlanAborted]);
|
|
92
|
-
const handleCommandAborted = React.useCallback(() => {
|
|
93
|
-
handleAborted('Request');
|
|
94
|
-
}, [handleAborted]);
|
|
74
|
+
const handleConfigAborted = React.useCallback(createConfigAbortedHandler(handleAborted), [handleAborted]);
|
|
75
|
+
const handlePlanAborted = React.useCallback(createPlanAbortedHandler(handleAborted), [handleAborted]);
|
|
76
|
+
const createPlanAbortHandler = React.useCallback(createPlanAbortHandlerFactory(handleAborted, handlePlanAborted), [handleAborted, handlePlanAborted]);
|
|
77
|
+
const handleCommandAborted = React.useCallback(createCommandAbortedHandler(handleAborted), [handleAborted]);
|
|
95
78
|
const handleRefinementAborted = React.useCallback(() => {
|
|
96
79
|
handleAborted('Plan refinement');
|
|
97
80
|
}, [handleAborted]);
|
|
98
|
-
const handleIntrospectAborted = React.useCallback(()
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
const
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
const [first] = currentQueue;
|
|
106
|
-
if (first.name === ComponentName.Introspect) {
|
|
107
|
-
addToTimeline(markAsDone(first), createFeedback(FeedbackType.Failed, FeedbackMessages.UnexpectedError, error));
|
|
108
|
-
}
|
|
109
|
-
exitApp(1);
|
|
110
|
-
return [];
|
|
111
|
-
});
|
|
112
|
-
}, [addToTimeline]);
|
|
113
|
-
const handleIntrospectComplete = React.useCallback((message, capabilities) => {
|
|
114
|
-
setQueue((currentQueue) => {
|
|
115
|
-
if (currentQueue.length === 0)
|
|
116
|
-
return currentQueue;
|
|
117
|
-
const [first] = currentQueue;
|
|
118
|
-
if (first.name === ComponentName.Introspect) {
|
|
119
|
-
// Don't add the Introspect component to timeline (it renders null)
|
|
120
|
-
// Only add the Report component
|
|
121
|
-
addToTimeline(createReportDefinition(message, capabilities));
|
|
122
|
-
}
|
|
123
|
-
exitApp(0);
|
|
124
|
-
return [];
|
|
125
|
-
});
|
|
126
|
-
}, [addToTimeline]);
|
|
127
|
-
const handleExecutionConfirmed = React.useCallback(() => {
|
|
128
|
-
setQueue((currentQueue) => {
|
|
129
|
-
if (currentQueue.length === 0)
|
|
130
|
-
return currentQueue;
|
|
131
|
-
const [first] = currentQueue;
|
|
132
|
-
if (first.name === ComponentName.Confirm) {
|
|
133
|
-
// Find the most recent Plan in timeline to get tasks
|
|
134
|
-
const currentTimeline = timelineRef.current;
|
|
135
|
-
const lastPlanIndex = [...currentTimeline]
|
|
136
|
-
.reverse()
|
|
137
|
-
.findIndex((item) => item.name === ComponentName.Plan);
|
|
138
|
-
const lastPlan = lastPlanIndex >= 0
|
|
139
|
-
? currentTimeline[currentTimeline.length - 1 - lastPlanIndex]
|
|
140
|
-
: null;
|
|
141
|
-
const tasks = lastPlan &&
|
|
142
|
-
lastPlan.name === ComponentName.Plan &&
|
|
143
|
-
'props' in lastPlan &&
|
|
144
|
-
lastPlan.props &&
|
|
145
|
-
'tasks' in lastPlan.props &&
|
|
146
|
-
Array.isArray(lastPlan.props.tasks)
|
|
147
|
-
? lastPlan.props.tasks
|
|
148
|
-
: [];
|
|
149
|
-
const allIntrospect = tasks.every((task) => task.type === TaskType.Introspect);
|
|
150
|
-
if (allIntrospect && tasks.length > 0) {
|
|
151
|
-
// Execute introspection
|
|
152
|
-
addToTimeline(markAsDone(first));
|
|
153
|
-
return [
|
|
154
|
-
createIntrospectDefinition(tasks, service, handleIntrospectError, handleIntrospectComplete, handleIntrospectAborted),
|
|
155
|
-
];
|
|
156
|
-
}
|
|
157
|
-
else {
|
|
158
|
-
// Regular execution - just exit for now
|
|
159
|
-
addToTimeline(markAsDone(first));
|
|
160
|
-
exitApp(0);
|
|
161
|
-
return [];
|
|
162
|
-
}
|
|
163
|
-
}
|
|
164
|
-
exitApp(0);
|
|
165
|
-
return [];
|
|
166
|
-
});
|
|
167
|
-
}, [
|
|
81
|
+
const handleIntrospectAborted = React.useCallback(createIntrospectAbortedHandler(handleAborted), [handleAborted]);
|
|
82
|
+
const handleIntrospectError = React.useCallback((error) => setQueue(createIntrospectErrorHandler(addToTimeline)(error)), [addToTimeline]);
|
|
83
|
+
const handleIntrospectComplete = React.useCallback((message, capabilities) => setQueue(createIntrospectCompleteHandler(addToTimeline)(message, capabilities)), [addToTimeline]);
|
|
84
|
+
const handleAnswerAborted = React.useCallback(createAnswerAbortedHandler(handleAborted), [handleAborted]);
|
|
85
|
+
const handleAnswerError = React.useCallback((error) => setQueue(createAnswerErrorHandler(addToTimeline)(error)), [addToTimeline]);
|
|
86
|
+
const handleAnswerComplete = React.useCallback((answer) => setQueue(createAnswerCompleteHandler(addToTimeline)(answer)), [addToTimeline]);
|
|
87
|
+
const handleExecutionConfirmed = React.useCallback(() => setQueue(createExecutionConfirmedHandler(timelineRef, addToTimeline, service, handleIntrospectError, handleIntrospectComplete, handleIntrospectAborted, handleAnswerError, handleAnswerComplete, handleAnswerAborted)()), [
|
|
168
88
|
addToTimeline,
|
|
169
89
|
service,
|
|
170
90
|
handleIntrospectError,
|
|
171
91
|
handleIntrospectComplete,
|
|
172
92
|
handleIntrospectAborted,
|
|
93
|
+
handleAnswerError,
|
|
94
|
+
handleAnswerComplete,
|
|
95
|
+
handleAnswerAborted,
|
|
173
96
|
]);
|
|
174
|
-
const handleExecutionCancelled = React.useCallback(() =>
|
|
175
|
-
|
|
176
|
-
if (currentQueue.length === 0)
|
|
177
|
-
return currentQueue;
|
|
178
|
-
const [first] = currentQueue;
|
|
179
|
-
if (first.name === ComponentName.Confirm) {
|
|
180
|
-
// Find the most recent Plan in timeline to check task types
|
|
181
|
-
const currentTimeline = timelineRef.current;
|
|
182
|
-
const lastPlanIndex = [...currentTimeline]
|
|
183
|
-
.reverse()
|
|
184
|
-
.findIndex((item) => item.name === ComponentName.Plan);
|
|
185
|
-
const lastPlan = lastPlanIndex >= 0
|
|
186
|
-
? currentTimeline[currentTimeline.length - 1 - lastPlanIndex]
|
|
187
|
-
: null;
|
|
188
|
-
const allIntrospect = lastPlan &&
|
|
189
|
-
lastPlan.name === ComponentName.Plan &&
|
|
190
|
-
'props' in lastPlan &&
|
|
191
|
-
lastPlan.props &&
|
|
192
|
-
'tasks' in lastPlan.props &&
|
|
193
|
-
Array.isArray(lastPlan.props.tasks) &&
|
|
194
|
-
lastPlan.props.tasks.every((task) => task.type === TaskType.Introspect);
|
|
195
|
-
const operation = allIntrospect ? 'introspection' : 'execution';
|
|
196
|
-
addToTimeline(markAsDone(first), createFeedback(FeedbackType.Aborted, getCancellationMessage(operation)));
|
|
197
|
-
}
|
|
198
|
-
exitApp(0);
|
|
199
|
-
return [];
|
|
200
|
-
});
|
|
201
|
-
}, [addToTimeline]);
|
|
202
|
-
const handlePlanSelectionConfirmed = React.useCallback(async (selectedTasks) => {
|
|
203
|
-
// Mark current plan as done and add refinement to queue
|
|
204
|
-
let refinementDef = null;
|
|
205
|
-
refinementDef = createRefinement(getRefiningMessage(), handleRefinementAborted);
|
|
206
|
-
setQueue((currentQueue) => {
|
|
207
|
-
if (currentQueue.length === 0)
|
|
208
|
-
return currentQueue;
|
|
209
|
-
const [first] = currentQueue;
|
|
210
|
-
if (first.name === ComponentName.Plan) {
|
|
211
|
-
addToTimeline(markAsDone(first));
|
|
212
|
-
}
|
|
213
|
-
// Add refinement to queue so it becomes the active component
|
|
214
|
-
return [refinementDef];
|
|
215
|
-
});
|
|
216
|
-
// Process refined command in background
|
|
217
|
-
try {
|
|
218
|
-
const refinedCommand = selectedTasks
|
|
219
|
-
.map((task) => {
|
|
220
|
-
const action = task.action.toLowerCase().replace(/,/g, ' -');
|
|
221
|
-
const type = task.type || 'execute';
|
|
222
|
-
return `${action} (type: ${type})`;
|
|
223
|
-
})
|
|
224
|
-
.join(', ');
|
|
225
|
-
const result = await service.processWithTool(refinedCommand, 'plan');
|
|
226
|
-
// Mark refinement as done and move to timeline
|
|
227
|
-
setQueue((currentQueue) => {
|
|
228
|
-
if (currentQueue.length > 0 &&
|
|
229
|
-
currentQueue[0].id === refinementDef.id) {
|
|
230
|
-
addToTimeline(markAsDone(currentQueue[0]));
|
|
231
|
-
}
|
|
232
|
-
return [];
|
|
233
|
-
});
|
|
234
|
-
// Show final execution plan with confirmation
|
|
235
|
-
const planDefinition = createPlanDefinition(result.message, result.tasks, createPlanAbortHandler(result.tasks), undefined);
|
|
236
|
-
const confirmDefinition = createConfirmDefinition(handleExecutionConfirmed, handleExecutionCancelled);
|
|
237
|
-
addToTimeline(planDefinition);
|
|
238
|
-
setQueue([confirmDefinition]);
|
|
239
|
-
}
|
|
240
|
-
catch (error) {
|
|
241
|
-
const errorMessage = error instanceof Error ? error.message : 'Unknown error occurred';
|
|
242
|
-
// Mark refinement as done and move to timeline before showing error
|
|
243
|
-
setQueue((currentQueue) => {
|
|
244
|
-
if (currentQueue.length > 0 &&
|
|
245
|
-
currentQueue[0].id === refinementDef.id) {
|
|
246
|
-
addToTimeline(markAsDone(currentQueue[0]));
|
|
247
|
-
}
|
|
248
|
-
return [];
|
|
249
|
-
});
|
|
250
|
-
addToTimeline(createFeedback(FeedbackType.Failed, FeedbackMessages.UnexpectedError, errorMessage));
|
|
251
|
-
exitApp(1);
|
|
252
|
-
}
|
|
253
|
-
}, [
|
|
97
|
+
const handleExecutionCancelled = React.useCallback(() => setQueue(createExecutionCancelledHandler(timelineRef, addToTimeline)()), [addToTimeline]);
|
|
98
|
+
const handlePlanSelectionConfirmed = React.useCallback(createPlanSelectionConfirmedHandler(addToTimeline, service, handleRefinementAborted, createPlanAbortHandler, handleExecutionConfirmed, handleExecutionCancelled, setQueue), [
|
|
254
99
|
addToTimeline,
|
|
255
100
|
service,
|
|
256
101
|
handleRefinementAborted,
|
|
@@ -258,62 +103,20 @@ export const Main = ({ app, command }) => {
|
|
|
258
103
|
handleExecutionConfirmed,
|
|
259
104
|
handleExecutionCancelled,
|
|
260
105
|
]);
|
|
261
|
-
const handleCommandComplete = React.useCallback((message, tasks) =>
|
|
262
|
-
setQueue((currentQueue) => {
|
|
263
|
-
if (currentQueue.length === 0)
|
|
264
|
-
return currentQueue;
|
|
265
|
-
const [first] = currentQueue;
|
|
266
|
-
// Check if tasks contain a Define task that requires user interaction
|
|
267
|
-
const hasDefineTask = tasks.some((task) => task.type === TaskType.Define);
|
|
268
|
-
if (first.name === ComponentName.Command) {
|
|
269
|
-
const planDefinition = createPlanDefinition(message, tasks, createPlanAbortHandler(tasks), hasDefineTask ? handlePlanSelectionConfirmed : undefined);
|
|
270
|
-
if (hasDefineTask) {
|
|
271
|
-
// Don't exit - keep the plan in the queue for interaction
|
|
272
|
-
addToTimeline(markAsDone(first));
|
|
273
|
-
return [planDefinition];
|
|
274
|
-
}
|
|
275
|
-
else {
|
|
276
|
-
// No define task - show plan and confirmation
|
|
277
|
-
const confirmDefinition = createConfirmDefinition(handleExecutionConfirmed, handleExecutionCancelled);
|
|
278
|
-
addToTimeline(markAsDone(first), planDefinition);
|
|
279
|
-
return [confirmDefinition];
|
|
280
|
-
}
|
|
281
|
-
}
|
|
282
|
-
exitApp(0);
|
|
283
|
-
return [];
|
|
284
|
-
});
|
|
285
|
-
}, [
|
|
106
|
+
const handleCommandComplete = React.useCallback((message, tasks) => setQueue(createCommandCompleteHandler(addToTimeline, createPlanAbortHandler, handlePlanSelectionConfirmed, handleExecutionConfirmed, handleExecutionCancelled)(message, tasks)), [
|
|
286
107
|
addToTimeline,
|
|
287
108
|
createPlanAbortHandler,
|
|
288
109
|
handlePlanSelectionConfirmed,
|
|
289
110
|
handleExecutionConfirmed,
|
|
290
111
|
handleExecutionCancelled,
|
|
291
112
|
]);
|
|
292
|
-
const handleConfigFinished = React.useCallback((config) =>
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
if (currentQueue.length === 0)
|
|
300
|
-
return currentQueue;
|
|
301
|
-
const [first, ...rest] = currentQueue;
|
|
302
|
-
if (first.name === ComponentName.Config) {
|
|
303
|
-
addToTimeline(markAsDone(first), createFeedback(FeedbackType.Succeeded, FeedbackMessages.ConfigurationComplete));
|
|
304
|
-
}
|
|
305
|
-
// Add command to queue if we have one
|
|
306
|
-
if (command) {
|
|
307
|
-
return [
|
|
308
|
-
...rest,
|
|
309
|
-
createCommandDefinition(command, newService, handleCommandError, handleCommandComplete, handleCommandAborted),
|
|
310
|
-
];
|
|
311
|
-
}
|
|
312
|
-
// No command - exit after showing completion message
|
|
313
|
-
exitApp(0);
|
|
314
|
-
return rest;
|
|
315
|
-
});
|
|
316
|
-
}, [addToTimeline, command, handleCommandError, handleCommandComplete]);
|
|
113
|
+
const handleConfigFinished = React.useCallback((config) => setQueue(createConfigFinishedHandler(addToTimeline, command, handleCommandError, handleCommandComplete, handleCommandAborted, setService)(config)), [
|
|
114
|
+
addToTimeline,
|
|
115
|
+
command,
|
|
116
|
+
handleCommandError,
|
|
117
|
+
handleCommandComplete,
|
|
118
|
+
handleCommandAborted,
|
|
119
|
+
]);
|
|
317
120
|
// Initialize queue on mount
|
|
318
121
|
React.useEffect(() => {
|
|
319
122
|
const hasConfig = !!service;
|