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.
Files changed (86) hide show
  1. package/LICENSE +7 -0
  2. package/package.json +72 -0
  3. package/src/agent/context.js +141 -0
  4. package/src/agent/loop/context-summary.js +196 -0
  5. package/src/agent/loop/directory-utils.js +102 -0
  6. package/src/agent/loop/local.js +196 -0
  7. package/src/agent/loop/loop-detection.js +288 -0
  8. package/src/agent/loop/stream-parser.js +515 -0
  9. package/src/agent/loop/tool-executor.js +470 -0
  10. package/src/agent/loop/verification.js +263 -0
  11. package/src/agent/loop/websocket.js +80 -0
  12. package/src/agent/prompt.js +259 -0
  13. package/src/agent/react-loop.js +697 -0
  14. package/src/agent/subagent.js +263 -0
  15. package/src/commands/config.js +53 -0
  16. package/src/commands/connect.js +190 -0
  17. package/src/commands/devices.js +121 -0
  18. package/src/commands/login.js +77 -0
  19. package/src/commands/logout.js +31 -0
  20. package/src/commands/mcp.js +258 -0
  21. package/src/commands/provider.js +633 -0
  22. package/src/commands/register.js +74 -0
  23. package/src/commands/run.js +150 -0
  24. package/src/commands/search.js +64 -0
  25. package/src/commands/session.js +57 -0
  26. package/src/commands/skills.js +54 -0
  27. package/src/commands/stop-subagent.js +58 -0
  28. package/src/index.js +208 -0
  29. package/src/llm/direct.js +317 -0
  30. package/src/memory/store.js +215 -0
  31. package/src/mock-readline.js +27 -0
  32. package/src/parser/dependencies.js +71 -0
  33. package/src/parser/markdown.js +505 -0
  34. package/src/parser/stream.js +96 -0
  35. package/src/prompts/modes/CODING.js +160 -0
  36. package/src/prompts/modes/GENERAL.js +105 -0
  37. package/src/prompts/modes/NETWORK.js +69 -0
  38. package/src/prompts/modes/SSH.js +53 -0
  39. package/src/prompts/systemPrompt.js +85 -0
  40. package/src/safety/check.js +210 -0
  41. package/src/services/crypto.js +78 -0
  42. package/src/services/executor.js +68 -0
  43. package/src/services/history.js +58 -0
  44. package/src/services/server-url.js +11 -0
  45. package/src/services/session.js +194 -0
  46. package/src/services/ssh.js +176 -0
  47. package/src/services/websocket.js +112 -0
  48. package/src/skills/loader.js +231 -0
  49. package/src/tools/browser.js +434 -0
  50. package/src/tools/local.js +1254 -0
  51. package/src/tools/mcp-client.js +209 -0
  52. package/src/tools/registry.js +132 -0
  53. package/src/tools/search-providers.js +237 -0
  54. package/src/tools/ssh.js +74 -0
  55. package/src/ui/App.js +2031 -0
  56. package/src/ui/animation.js +47 -0
  57. package/src/ui/components/AskUserDialog.js +33 -0
  58. package/src/ui/components/ConfirmationDialog.js +45 -0
  59. package/src/ui/components/DiffView.js +201 -0
  60. package/src/ui/components/Header.js +157 -0
  61. package/src/ui/components/HistoryPicker.js +130 -0
  62. package/src/ui/components/InputShell.js +22 -0
  63. package/src/ui/components/MessageHistory.js +1200 -0
  64. package/src/ui/components/ModalPanel.js +40 -0
  65. package/src/ui/components/ModePicker.js +161 -0
  66. package/src/ui/components/PlanDialog.js +48 -0
  67. package/src/ui/components/ProviderMenu.js +1095 -0
  68. package/src/ui/components/SavePicker.js +106 -0
  69. package/src/ui/components/SelectMenu.js +194 -0
  70. package/src/ui/components/SlashMenu.js +168 -0
  71. package/src/ui/components/SubagentPanel.js +138 -0
  72. package/src/ui/components/TextInputSafe.js +117 -0
  73. package/src/ui/components/TodoPanel.js +54 -0
  74. package/src/ui/components/ToolExecution.js +261 -0
  75. package/src/ui/components/TranscriptViewport.js +99 -0
  76. package/src/ui/diff.js +249 -0
  77. package/src/ui/h.js +7 -0
  78. package/src/ui/mouse-scroll.js +63 -0
  79. package/src/ui/slash-picker.js +58 -0
  80. package/src/ui/terminal.js +41 -0
  81. package/src/ui/theme.js +5 -0
  82. package/src/ui/welcome.js +12 -0
  83. package/src/utils/constants.js +231 -0
  84. package/src/utils/helpers.js +154 -0
  85. package/src/utils/logger.js +81 -0
  86. 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
+ }