osai-agent 4.0.0
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/LICENSE +7 -0
- package/package.json +72 -0
- package/src/agent/context.js +141 -0
- package/src/agent/loop/context-summary.js +196 -0
- package/src/agent/loop/directory-utils.js +102 -0
- package/src/agent/loop/local.js +196 -0
- package/src/agent/loop/loop-detection.js +288 -0
- package/src/agent/loop/stream-parser.js +515 -0
- package/src/agent/loop/tool-executor.js +470 -0
- package/src/agent/loop/verification.js +263 -0
- package/src/agent/loop/websocket.js +80 -0
- package/src/agent/prompt.js +259 -0
- package/src/agent/react-loop.js +697 -0
- package/src/agent/subagent.js +263 -0
- package/src/commands/config.js +53 -0
- package/src/commands/connect.js +190 -0
- package/src/commands/devices.js +121 -0
- package/src/commands/login.js +77 -0
- package/src/commands/logout.js +31 -0
- package/src/commands/mcp.js +258 -0
- package/src/commands/provider.js +633 -0
- package/src/commands/register.js +74 -0
- package/src/commands/run.js +150 -0
- package/src/commands/search.js +64 -0
- package/src/commands/session.js +57 -0
- package/src/commands/skills.js +54 -0
- package/src/commands/stop-subagent.js +58 -0
- package/src/index.js +208 -0
- package/src/llm/direct.js +317 -0
- package/src/memory/store.js +215 -0
- package/src/mock-readline.js +27 -0
- package/src/parser/dependencies.js +71 -0
- package/src/parser/markdown.js +505 -0
- package/src/parser/stream.js +96 -0
- package/src/prompts/modes/CODING.js +160 -0
- package/src/prompts/modes/GENERAL.js +105 -0
- package/src/prompts/modes/NETWORK.js +69 -0
- package/src/prompts/modes/SSH.js +53 -0
- package/src/prompts/systemPrompt.js +85 -0
- package/src/safety/check.js +210 -0
- package/src/services/crypto.js +78 -0
- package/src/services/executor.js +68 -0
- package/src/services/history.js +58 -0
- package/src/services/server-url.js +11 -0
- package/src/services/session.js +194 -0
- package/src/services/ssh.js +176 -0
- package/src/services/websocket.js +112 -0
- package/src/skills/loader.js +231 -0
- package/src/tools/browser.js +434 -0
- package/src/tools/local.js +1254 -0
- package/src/tools/mcp-client.js +209 -0
- package/src/tools/registry.js +132 -0
- package/src/tools/search-providers.js +237 -0
- package/src/tools/ssh.js +74 -0
- package/src/ui/App.js +2031 -0
- package/src/ui/animation.js +47 -0
- package/src/ui/components/AskUserDialog.js +33 -0
- package/src/ui/components/ConfirmationDialog.js +45 -0
- package/src/ui/components/DiffView.js +201 -0
- package/src/ui/components/Header.js +157 -0
- package/src/ui/components/HistoryPicker.js +130 -0
- package/src/ui/components/InputShell.js +22 -0
- package/src/ui/components/MessageHistory.js +1200 -0
- package/src/ui/components/ModalPanel.js +40 -0
- package/src/ui/components/ModePicker.js +161 -0
- package/src/ui/components/PlanDialog.js +48 -0
- package/src/ui/components/ProviderMenu.js +1095 -0
- package/src/ui/components/SavePicker.js +106 -0
- package/src/ui/components/SelectMenu.js +194 -0
- package/src/ui/components/SlashMenu.js +168 -0
- package/src/ui/components/SubagentPanel.js +138 -0
- package/src/ui/components/TextInputSafe.js +117 -0
- package/src/ui/components/TodoPanel.js +54 -0
- package/src/ui/components/ToolExecution.js +261 -0
- package/src/ui/components/TranscriptViewport.js +99 -0
- package/src/ui/diff.js +249 -0
- package/src/ui/h.js +7 -0
- package/src/ui/mouse-scroll.js +63 -0
- package/src/ui/slash-picker.js +58 -0
- package/src/ui/terminal.js +41 -0
- package/src/ui/theme.js +5 -0
- package/src/ui/welcome.js +12 -0
- package/src/utils/constants.js +231 -0
- package/src/utils/helpers.js +154 -0
- package/src/utils/logger.js +81 -0
- package/src/utils/sound.js +33 -0
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { Box } from 'ink';
|
|
3
|
+
import { h } from '../h.js';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* ModalPanel
|
|
7
|
+
* -----------
|
|
8
|
+
* Opaque wrapper used for all modal/dialog overlays (confirmation, ask-user,
|
|
9
|
+
* plan, slash menu, mode picker, provider menu, save picker, history picker).
|
|
10
|
+
*
|
|
11
|
+
* Goal: replace the previous `position: 'absolute', bottom: 0` overlays that
|
|
12
|
+
* left the underlying TranscriptViewport cells visible (Ink/Yoga positions
|
|
13
|
+
* absolute boxes on top of siblings but does not clear the cells underneath,
|
|
14
|
+
* producing the mixed output the user observed).
|
|
15
|
+
*
|
|
16
|
+
* Layout rules:
|
|
17
|
+
* - Renders inside normal flex flow (no `position: 'absolute'`).
|
|
18
|
+
* - The caller decides whether the panel sits in the *middle* of the column
|
|
19
|
+
* (agent-blocking modals: confirmation, askUserDetails, planDetails —
|
|
20
|
+
* transcript is hidden) or in the *footer* (pickers — transcript shrinks
|
|
21
|
+
* but remains visible above the panel).
|
|
22
|
+
* - The opaque backgroundColor prevents the transcript from bleeding
|
|
23
|
+
* through the panel's transparent cells (matches the existing palette
|
|
24
|
+
* used in MessageHistory.js).
|
|
25
|
+
*/
|
|
26
|
+
export function ModalPanel({ children, flexGrow = 0, flexShrink = 0, marginY = 1, alignItems = 'center' }) {
|
|
27
|
+
return h(
|
|
28
|
+
Box,
|
|
29
|
+
{
|
|
30
|
+
width: '100%',
|
|
31
|
+
flexDirection: 'column',
|
|
32
|
+
alignItems,
|
|
33
|
+
flexGrow,
|
|
34
|
+
flexShrink,
|
|
35
|
+
marginY,
|
|
36
|
+
backgroundColor: '#0f0f14',
|
|
37
|
+
},
|
|
38
|
+
children
|
|
39
|
+
);
|
|
40
|
+
}
|
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
import React, { useState, useEffect, useMemo } from 'react';
|
|
2
|
+
import { Box, Text, useInput, useWindowSize } from 'ink';
|
|
3
|
+
import { h } from '../h.js';
|
|
4
|
+
import { InputShell } from './InputShell.js';
|
|
5
|
+
import { isOnlySgrMouseInput } from '../mouse-scroll.js';
|
|
6
|
+
|
|
7
|
+
const ALL_MODES = [
|
|
8
|
+
{ name: 'GENERAL', desc: 'System administration mode', value: 'GENERAL' },
|
|
9
|
+
{ name: 'CODING', desc: 'Software engineering mode', value: 'CODING' },
|
|
10
|
+
{ name: 'NETWORK', desc: 'Remote network device management', value: 'NETWORK' },
|
|
11
|
+
{ name: 'SSH', desc: 'Remote execution via SSH', value: 'SSH' },
|
|
12
|
+
{ name: 'PLAN', desc: 'Plan mode — read-only, no file modifications', value: 'PLAN' },
|
|
13
|
+
{ name: 'EXEC', desc: 'Exec mode — file modifications allowed', value: 'EXEC' },
|
|
14
|
+
];
|
|
15
|
+
|
|
16
|
+
const MODE_ICONS = {
|
|
17
|
+
GENERAL: '⚙',
|
|
18
|
+
CODING: '⟨/⟩',
|
|
19
|
+
NETWORK: '◈',
|
|
20
|
+
SSH: '⇌',
|
|
21
|
+
PLAN: '◷',
|
|
22
|
+
EXEC: '▶',
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
export function ModePicker({ visible, onSelect, onCancel, currentMode, currentExecutionMode, hiddenModes }) {
|
|
26
|
+
const [query, setQuery] = useState('');
|
|
27
|
+
const [cursor, setCursor] = useState(0);
|
|
28
|
+
const hidden = hiddenModes || [];
|
|
29
|
+
|
|
30
|
+
const MODES = useMemo(() => ALL_MODES.filter(m => !hidden.includes(m.value)), [hidden]);
|
|
31
|
+
|
|
32
|
+
const filtered = useMemo(() => {
|
|
33
|
+
if (!query) return MODES;
|
|
34
|
+
const q = query.toLowerCase();
|
|
35
|
+
return MODES.filter(
|
|
36
|
+
(m) => m.name.toLowerCase().includes(q) || m.desc.toLowerCase().includes(q)
|
|
37
|
+
);
|
|
38
|
+
}, [query, MODES]);
|
|
39
|
+
|
|
40
|
+
useEffect(() => {
|
|
41
|
+
// Pre-select current mode if provided
|
|
42
|
+
if (visible) {
|
|
43
|
+
const idx = currentMode
|
|
44
|
+
? filtered.findIndex(m => m.value === currentMode)
|
|
45
|
+
: -1;
|
|
46
|
+
if (idx >= 0) {
|
|
47
|
+
setCursor(idx);
|
|
48
|
+
} else if (currentExecutionMode) {
|
|
49
|
+
const eIdx = filtered.findIndex(m => m.value === currentExecutionMode);
|
|
50
|
+
if (eIdx >= 0) setCursor(eIdx);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
}, [visible]);
|
|
54
|
+
|
|
55
|
+
useEffect(() => {
|
|
56
|
+
if (cursor >= filtered.length) setCursor(Math.max(0, filtered.length - 1));
|
|
57
|
+
}, [filtered.length]);
|
|
58
|
+
|
|
59
|
+
useInput((input, key) => {
|
|
60
|
+
if (!visible) return;
|
|
61
|
+
|
|
62
|
+
if (key.escape) {
|
|
63
|
+
setQuery('');
|
|
64
|
+
setCursor(0);
|
|
65
|
+
onCancel();
|
|
66
|
+
return;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
if (key.upArrow) {
|
|
70
|
+
setCursor((c) => (c > 0 ? c - 1 : filtered.length - 1));
|
|
71
|
+
return;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
if (key.downArrow) {
|
|
75
|
+
setCursor((c) => (c < filtered.length - 1 ? c + 1 : 0));
|
|
76
|
+
return;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
if (key.return) {
|
|
80
|
+
if (filtered.length > 0) {
|
|
81
|
+
onSelect(filtered[cursor]);
|
|
82
|
+
}
|
|
83
|
+
setQuery('');
|
|
84
|
+
setCursor(0);
|
|
85
|
+
return;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
if (key.backspace || key.delete) {
|
|
89
|
+
setQuery((q) => q.slice(0, -1));
|
|
90
|
+
setCursor(0);
|
|
91
|
+
return;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
if (isOnlySgrMouseInput(input)) return;
|
|
95
|
+
if (input && !key.ctrl && !key.meta) {
|
|
96
|
+
setQuery((q) => q + input);
|
|
97
|
+
setCursor(0);
|
|
98
|
+
}
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
if (!visible) return null;
|
|
102
|
+
|
|
103
|
+
const separator = '─'.repeat(48);
|
|
104
|
+
|
|
105
|
+
return h(
|
|
106
|
+
Box,
|
|
107
|
+
{ flexDirection: 'column', borderStyle: 'round', borderColor: '#2a2e3f', paddingX: 1, paddingY: 0, marginY: 1 },
|
|
108
|
+
|
|
109
|
+
h(Text, { color: '#7aa2f7', bold: true }, ' Select execution mode'),
|
|
110
|
+
h(Text, { color: '#3b3f52' }, separator),
|
|
111
|
+
|
|
112
|
+
// Search bar
|
|
113
|
+
h(InputShell, { flexDirection: 'row', paddingY: 0, marginY: 0 },
|
|
114
|
+
h(Text, { color: '#e0af68' }, ' Search: '),
|
|
115
|
+
h(Text, { color: '#c0caf5' }, query || ' '),
|
|
116
|
+
h(Text, { color: '#565f89' }, query ? '' : ' type to filter modes')
|
|
117
|
+
),
|
|
118
|
+
h(Text, { color: '#3b3f52' }, separator),
|
|
119
|
+
|
|
120
|
+
// Items
|
|
121
|
+
...filtered.map((m, i) => {
|
|
122
|
+
const isHighlighted = i === cursor;
|
|
123
|
+
const isCurrent = m.value === currentMode || m.value === currentExecutionMode;
|
|
124
|
+
const icon = MODE_ICONS[m.value] || '●';
|
|
125
|
+
const prefix = isHighlighted ? h(Text, { color: '#9ece6a' }, ' ▸ ') : h(Text, { color: '#3b3f52' }, ' ');
|
|
126
|
+
const currentMark = isCurrent ? ' ◀ current' : '';
|
|
127
|
+
|
|
128
|
+
return h(
|
|
129
|
+
Box,
|
|
130
|
+
{ key: i },
|
|
131
|
+
prefix,
|
|
132
|
+
h(Text, { color: isHighlighted ? '#9ece6a' : '#565f89' }, `${icon} `),
|
|
133
|
+
h(
|
|
134
|
+
Text,
|
|
135
|
+
isHighlighted
|
|
136
|
+
? { color: '#ffffff', bold: true, backgroundColor: '#2a3a5c' }
|
|
137
|
+
: isCurrent
|
|
138
|
+
? { color: '#7aa2f7', bold: true }
|
|
139
|
+
: { color: '#9aa5ce' },
|
|
140
|
+
` ${m.name.padEnd(10)} ${isHighlighted ? m.desc : ''}${currentMark}`
|
|
141
|
+
)
|
|
142
|
+
);
|
|
143
|
+
}),
|
|
144
|
+
|
|
145
|
+
h(Text, { color: '#3b3f52' }, separator),
|
|
146
|
+
|
|
147
|
+
// Footer
|
|
148
|
+
h(
|
|
149
|
+
Box,
|
|
150
|
+
{ flexDirection: 'row' },
|
|
151
|
+
h(Text, { color: '#9ece6a' }, ' ↑↓'),
|
|
152
|
+
h(Text, { color: '#565f89' }, ' Navigate'),
|
|
153
|
+
h(Text, { color: '#3b3f52' }, ' '),
|
|
154
|
+
h(Text, { color: '#9ece6a' }, ' Enter'),
|
|
155
|
+
h(Text, { color: '#565f89' }, ' Select'),
|
|
156
|
+
h(Text, { color: '#3b3f52' }, ' '),
|
|
157
|
+
h(Text, { color: '#9ece6a' }, ' Esc'),
|
|
158
|
+
h(Text, { color: '#565f89' }, ' Cancel')
|
|
159
|
+
)
|
|
160
|
+
);
|
|
161
|
+
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import React, { useState } from 'react';
|
|
2
|
+
import { Box, Text, useInput, useWindowSize } from 'ink';
|
|
3
|
+
import { ScrollView } from 'ink-scroll-view';
|
|
4
|
+
import { h } from '../h.js';
|
|
5
|
+
import { InputShell } from './InputShell.js';
|
|
6
|
+
|
|
7
|
+
export function PlanDialog({ plan, onConfirm }) {
|
|
8
|
+
const { rows, columns } = useWindowSize();
|
|
9
|
+
const [value, setValue] = useState('');
|
|
10
|
+
|
|
11
|
+
useInput((input, key) => {
|
|
12
|
+
if (key.return) {
|
|
13
|
+
const approved = value.toLowerCase() === 'y' || value.toLowerCase() === 'yes';
|
|
14
|
+
onConfirm(approved);
|
|
15
|
+
setValue('');
|
|
16
|
+
return;
|
|
17
|
+
}
|
|
18
|
+
if (key.backspace || key.delete) {
|
|
19
|
+
setValue(v => v.slice(0, -1));
|
|
20
|
+
return;
|
|
21
|
+
}
|
|
22
|
+
if (key.upArrow || key.downArrow || key.leftArrow || key.rightArrow) return;
|
|
23
|
+
if (key.pageUp || key.pageDown || key.home || key.end) return;
|
|
24
|
+
if (input && input.length === 1 && input >= ' ' && input <= '~') {
|
|
25
|
+
setValue(v => v + input);
|
|
26
|
+
}
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
const lines = (plan || '').split('\n');
|
|
30
|
+
|
|
31
|
+
return h(Box, { flexDirection: 'column', paddingY: 1, borderStyle: 'round', borderColor: '#7dcfff', flexGrow: 1 },
|
|
32
|
+
h(Text, { color: '#7dcfff', bold: true }, 'Execution Plan'),
|
|
33
|
+
h(Box, { flexGrow: 1, flexShrink: 1, minHeight: 0, overflow: 'hidden', marginTop: 1 },
|
|
34
|
+
h(ScrollView, { flexGrow: 1, width: '100%' },
|
|
35
|
+
...lines.map((line, i) =>
|
|
36
|
+
h(Text, { key: i, color: '#c0caf5' }, ` ${line}`)
|
|
37
|
+
)
|
|
38
|
+
)
|
|
39
|
+
),
|
|
40
|
+
h(Box, { marginTop: 1 },
|
|
41
|
+
h(Text, { color: '#2a2e3f' }, '\u2500'.repeat(Math.min((columns || 80) - 4, 78)))
|
|
42
|
+
),
|
|
43
|
+
h(InputShell, { flexDirection: 'row', marginTop: 1, paddingX: 1 },
|
|
44
|
+
h(Text, { color: '#e0af68' }, 'Approve plan? (y/N): '),
|
|
45
|
+
h(Text, { color: 'white' }, value || ' ')
|
|
46
|
+
)
|
|
47
|
+
);
|
|
48
|
+
}
|