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.
@@ -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
+ };
@@ -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
+ }
@@ -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
- return (_jsxs(Box, { alignSelf: "flex-start", flexDirection: "column", children: [_jsxs(Box, { children: [_jsxs(Text, { color: "gray", 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
+ 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
  }
@@ -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
- return _jsx(Plan, { ...def.props, debug: debug });
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 '../types/colors.js';
5
+ import { Colors } from '../services/colors.js';
6
6
  export var StepType;
7
7
  (function (StepType) {
8
8
  StepType["Text"] = "text";
@@ -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 '../types/colors.js';
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: "gray", children: ["> ", options[selectedIndex].label] }) })] }));
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
  }) })] })] }));
@@ -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 { FeedbackColors } from '../types/colors.js';
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 = FeedbackColors[type];
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
  }
@@ -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, descriptionColor, type, typeColor, showType = false, }) {
5
- return (_jsxs(Box, { children: [_jsx(Text, { color: descriptionColor, children: description }), showType && (_jsxs(_Fragment, { children: [_jsx(Separator, {}), _jsx(Text, { color: typeColor, children: type })] }))] }));
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 { Label } from './Label.js';
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(Label, { description: item.description.text, descriptionColor: descriptionColor, type: item.type.text, typeColor: typeColor, showType: showType })] }), item.children && item.children.length > 0 && (_jsx(List, { items: item.children, level: level + 1, highlightedIndex: shouldHighlightChildren ? highlightedIndex : null, showType: showType }))] }, index));
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 { ComponentName, FeedbackType, TaskType, } from '../types/types.js';
4
+ import { FeedbackType } from '../types/types.js';
5
5
  import { createAnthropicService, } from '../services/anthropic.js';
6
- import { getConfigurationRequiredMessage, hasValidAnthropicKey, loadConfig, loadDebugSetting, saveAnthropicConfig, saveDebugSetting, } from '../services/config.js';
7
- import { FeedbackMessages, getCancellationMessage, getRefiningMessage, } from '../services/messages.js';
8
- import { createCommandDefinition, createConfirmDefinition, createConfigDefinition, createFeedback, createIntrospectDefinition, createMessage, createPlanDefinition, createRefinement, createReportDefinition, createWelcomeDefinition, isStateless, markAsDone, } from '../services/components.js';
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
- handleAborted('Configuration');
81
- }, [handleAborted]);
82
- const handlePlanAborted = React.useCallback(() => {
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
- handleAborted('Introspection');
100
- }, [handleAborted]);
101
- const handleIntrospectError = React.useCallback((error) => {
102
- setQueue((currentQueue) => {
103
- if (currentQueue.length === 0)
104
- return currentQueue;
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
- setQueue((currentQueue) => {
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
- const anthropicConfig = config;
294
- saveAnthropicConfig(anthropicConfig);
295
- const newService = createAnthropicService(anthropicConfig);
296
- setService(newService);
297
- // Complete config component and add command if present
298
- setQueue((currentQueue) => {
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;