@darksol/terminal 0.8.1 → 0.9.1
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/README.md +59 -3
- package/package.json +1 -1
- package/src/agent/index.js +134 -0
- package/src/agent/loop.js +190 -0
- package/src/agent/tools.js +311 -0
- package/src/cli.js +194 -3
- package/src/config/keys.js +9 -1
- package/src/config/store.js +29 -0
- package/src/llm/engine.js +65 -91
- package/src/memory/index.js +275 -0
- package/src/setup/wizard.js +29 -11
- package/src/soul/index.js +139 -0
- package/src/web/commands.js +97 -3
- package/src/web/server.js +15 -0
package/README.md
CHANGED
|
@@ -15,7 +15,7 @@ A unified CLI for market intel, trading, AI-powered analysis, on-chain oracle, c
|
|
|
15
15
|
[](https://www.gnu.org/licenses/gpl-3.0)
|
|
16
16
|
[](https://nodejs.org/)
|
|
17
17
|
|
|
18
|
-
- Current release: **0.
|
|
18
|
+
- Current release: **0.9.1**
|
|
19
19
|
- Changelog: `CHANGELOG.md`
|
|
20
20
|
|
|
21
21
|
## Install
|
|
@@ -56,9 +56,22 @@ darksol bridge send --from base --to arbitrum --token ETH -a 0.1
|
|
|
56
56
|
darksol bridge status 0xTxHash...
|
|
57
57
|
darksol bridge chains
|
|
58
58
|
|
|
59
|
-
#
|
|
59
|
+
# Set up your agent identity
|
|
60
|
+
darksol soul
|
|
61
|
+
|
|
62
|
+
# AI trading assistant (now with personality + memory)
|
|
60
63
|
darksol ai chat
|
|
61
64
|
|
|
65
|
+
# View/search persistent memories
|
|
66
|
+
darksol memory show
|
|
67
|
+
darksol memory search "preferred chain"
|
|
68
|
+
|
|
69
|
+
# Autonomous agent task (ReAct loop)
|
|
70
|
+
darksol agent task "check AERO price and tell me if it's above $2"
|
|
71
|
+
darksol agent task "analyze my portfolio" --max-steps 5
|
|
72
|
+
darksol agent task "swap 0.1 ETH for USDC" --allow-actions
|
|
73
|
+
darksol agent plan "DCA strategy for AERO"
|
|
74
|
+
|
|
62
75
|
# Agent email
|
|
63
76
|
darksol mail setup
|
|
64
77
|
darksol mail send --to user@example.com --subject "Hello"
|
|
@@ -114,6 +127,9 @@ ai <prompt> # chat with trading assistant
|
|
|
114
127
|
| `trade` | Swap via LI.FI (31 DEXs) + Uniswap V3 fallback, snipe | Gas only |
|
|
115
128
|
| `bridge` | Cross-chain bridge via LI.FI (60 chains, 27 bridges) | Gas only |
|
|
116
129
|
| `dca` | Dollar-cost averaging engine | Gas only |
|
|
130
|
+
| `soul` | Agent identity & personality configuration | Free |
|
|
131
|
+
| `memory` | Persistent cross-session memory store | Free |
|
|
132
|
+
| `agent task` | Autonomous ReAct agent loop with tool use | Provider dependent |
|
|
117
133
|
| `ai` | LLM-powered trading assistant & intent execution | Provider dependent |
|
|
118
134
|
| `agent` | Secure agent signer (PK-isolated proxy) | Free |
|
|
119
135
|
| `keys` | Encrypted API key vault (LLMs/data/RPCs) | Free |
|
|
@@ -190,9 +206,49 @@ darksol agent docs
|
|
|
190
206
|
|
|
191
207
|
---
|
|
192
208
|
|
|
209
|
+
## 👤 Agent Soul System
|
|
210
|
+
|
|
211
|
+
Give your terminal agent a name, personality, and persistent memory.
|
|
212
|
+
|
|
213
|
+
```bash
|
|
214
|
+
# First-run setup (or run anytime)
|
|
215
|
+
darksol soul
|
|
216
|
+
|
|
217
|
+
# View current identity
|
|
218
|
+
darksol soul show
|
|
219
|
+
|
|
220
|
+
# Reset and reconfigure
|
|
221
|
+
darksol soul reset
|
|
222
|
+
```
|
|
223
|
+
|
|
224
|
+
**What it does:**
|
|
225
|
+
- **Your name** — the agent addresses you personally
|
|
226
|
+
- **Agent name** — name your AI (default: Darksol)
|
|
227
|
+
- **Tone** — professional, casual, hacker, friendly, sarcastic, or custom freeform
|
|
228
|
+
- Persists across sessions — your agent remembers who it is
|
|
229
|
+
- Auto-injected into every LLM call as a system prompt
|
|
230
|
+
|
|
231
|
+
**Session memory:** Conversations maintain context (up to 20 turns). When the limit is hit, older turns are summarized by the LLM — no hard context cliff.
|
|
232
|
+
|
|
233
|
+
**Persistent memory:** Important facts, preferences, and decisions are auto-extracted and stored to disk (`~/.darksol/memory/`). Your agent learns over time.
|
|
234
|
+
|
|
235
|
+
```bash
|
|
236
|
+
# View recent memories
|
|
237
|
+
darksol memory show --limit 20
|
|
238
|
+
|
|
239
|
+
# Search memories
|
|
240
|
+
darksol memory search "slippage preference"
|
|
241
|
+
|
|
242
|
+
# Export / clear
|
|
243
|
+
darksol memory export my-memories.json
|
|
244
|
+
darksol memory clear
|
|
245
|
+
```
|
|
246
|
+
|
|
247
|
+
---
|
|
248
|
+
|
|
193
249
|
## 🧠 AI Trading Assistant
|
|
194
250
|
|
|
195
|
-
Natural language trading powered by multi-provider LLM support.
|
|
251
|
+
Natural language trading powered by multi-provider LLM support — now with soul personality and memory context.
|
|
196
252
|
|
|
197
253
|
```bash
|
|
198
254
|
# Interactive chat with live market data
|
package/package.json
CHANGED
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
import { getAllConfig, getConfig, setConfig } from '../config/store.js';
|
|
2
|
+
import { createLLM } from '../llm/engine.js';
|
|
3
|
+
import { getRecentMemories, saveMemory } from '../memory/index.js';
|
|
4
|
+
import { createToolExecutor, createToolRegistry, listTools } from './tools.js';
|
|
5
|
+
import { DEFAULT_MAX_STEPS, runAgentLoop } from './loop.js';
|
|
6
|
+
|
|
7
|
+
const AGENT_STATE_KEY = 'agentState';
|
|
8
|
+
|
|
9
|
+
function agentSystemPrompt({ goal, allowActions, maxSteps, recentMemories }) {
|
|
10
|
+
const cfg = getAllConfig();
|
|
11
|
+
return [
|
|
12
|
+
'You are DARKSOL Terminal agent mode.',
|
|
13
|
+
'Operate in a bounded ReAct loop and keep outputs terse and operational.',
|
|
14
|
+
`Goal: ${goal}`,
|
|
15
|
+
`Max steps: ${maxSteps}`,
|
|
16
|
+
`Actions enabled: ${allowActions ? 'yes' : 'no'}`,
|
|
17
|
+
`Chain: ${cfg.chain || 'base'}`,
|
|
18
|
+
`Active wallet: ${cfg.activeWallet || '(none)'}`,
|
|
19
|
+
`Slippage: ${cfg.slippage || 0.5}%`,
|
|
20
|
+
recentMemories.length
|
|
21
|
+
? `Recent memories:\n${recentMemories.map((memory) => `- [${memory.category}] ${memory.content}`).join('\n')}`
|
|
22
|
+
: 'Recent memories: none',
|
|
23
|
+
'Prefer read-only verification before any action.',
|
|
24
|
+
].join('\n\n');
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
async function persistAgentMemories(result) {
|
|
28
|
+
const outcome = result.final || result.summary;
|
|
29
|
+
if (outcome) {
|
|
30
|
+
await saveMemory(`Agent outcome: ${outcome}`, 'lesson', 'agent');
|
|
31
|
+
}
|
|
32
|
+
if (result.goal) {
|
|
33
|
+
await saveMemory(`Agent worked on: ${result.goal}`, 'decision', 'agent');
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function saveAgentStatus(state) {
|
|
38
|
+
const previous = getConfig(AGENT_STATE_KEY) || {};
|
|
39
|
+
setConfig(AGENT_STATE_KEY, {
|
|
40
|
+
...previous,
|
|
41
|
+
...state,
|
|
42
|
+
updatedAt: new Date().toISOString(),
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export async function planAgentGoal(goal, opts = {}) {
|
|
47
|
+
const llm = opts.llm || await createLLM(opts);
|
|
48
|
+
const memories = await getRecentMemories(5);
|
|
49
|
+
llm.setSystemPrompt(agentSystemPrompt({
|
|
50
|
+
goal,
|
|
51
|
+
allowActions: false,
|
|
52
|
+
maxSteps: opts.maxSteps || DEFAULT_MAX_STEPS,
|
|
53
|
+
recentMemories: memories,
|
|
54
|
+
}));
|
|
55
|
+
|
|
56
|
+
const result = await llm.json([
|
|
57
|
+
'Create a concise execution plan for this goal.',
|
|
58
|
+
`Goal: ${goal}`,
|
|
59
|
+
'Return JSON only: {"summary":"string","steps":["step 1","step 2"]}',
|
|
60
|
+
].join('\n\n'), {
|
|
61
|
+
ephemeral: true,
|
|
62
|
+
skipMemoryExtraction: true,
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
const parsed = result.parsed || {};
|
|
66
|
+
const plan = {
|
|
67
|
+
goal,
|
|
68
|
+
summary: String(parsed.summary || `Plan for: ${goal}`),
|
|
69
|
+
steps: Array.isArray(parsed.steps) ? parsed.steps.map((step) => String(step)) : [],
|
|
70
|
+
createdAt: new Date().toISOString(),
|
|
71
|
+
};
|
|
72
|
+
|
|
73
|
+
saveAgentStatus({
|
|
74
|
+
status: 'planned',
|
|
75
|
+
goal,
|
|
76
|
+
summary: plan.summary,
|
|
77
|
+
plan: plan.steps,
|
|
78
|
+
allowActions: false,
|
|
79
|
+
stepsTaken: 0,
|
|
80
|
+
startedAt: null,
|
|
81
|
+
completedAt: null,
|
|
82
|
+
});
|
|
83
|
+
await saveMemory(`Agent plan: ${plan.summary}`, 'decision', 'agent');
|
|
84
|
+
return plan;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
export async function runAgentTask(goal, opts = {}) {
|
|
88
|
+
const maxSteps = Number(opts.maxSteps) > 0 ? Number(opts.maxSteps) : DEFAULT_MAX_STEPS;
|
|
89
|
+
const allowActions = Boolean(opts.allowActions);
|
|
90
|
+
const llm = opts.llm || await createLLM(opts);
|
|
91
|
+
const recentMemories = await getRecentMemories(5);
|
|
92
|
+
const registry = opts.registry || createToolRegistry(opts.toolDeps);
|
|
93
|
+
const tools = listTools(registry);
|
|
94
|
+
const executeTool = opts.executeTool || createToolExecutor({
|
|
95
|
+
registry,
|
|
96
|
+
allowActions,
|
|
97
|
+
onEvent: opts.onToolEvent,
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
llm.setSystemPrompt(agentSystemPrompt({ goal, allowActions, maxSteps, recentMemories }));
|
|
101
|
+
|
|
102
|
+
const result = await runAgentLoop({
|
|
103
|
+
goal,
|
|
104
|
+
llm,
|
|
105
|
+
tools,
|
|
106
|
+
executeTool,
|
|
107
|
+
maxSteps,
|
|
108
|
+
allowActions,
|
|
109
|
+
onProgress: opts.onProgress,
|
|
110
|
+
saveOutcome: async (state) => {
|
|
111
|
+
saveAgentStatus({
|
|
112
|
+
status: state.status,
|
|
113
|
+
goal: state.goal,
|
|
114
|
+
summary: state.final,
|
|
115
|
+
stepsTaken: state.stepsTaken,
|
|
116
|
+
maxSteps: state.maxSteps,
|
|
117
|
+
allowActions: state.allowActions,
|
|
118
|
+
startedAt: state.startedAt,
|
|
119
|
+
completedAt: state.completedAt,
|
|
120
|
+
stopReason: state.stopReason,
|
|
121
|
+
lastTool: state.steps[state.steps.length - 1]?.action || null,
|
|
122
|
+
plan: state.steps.map((step) => `${step.step}. ${step.action}`),
|
|
123
|
+
});
|
|
124
|
+
await persistAgentMemories(state);
|
|
125
|
+
},
|
|
126
|
+
persistStatus: async (state) => saveAgentStatus(state),
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
return result;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
export function getAgentStatus() {
|
|
133
|
+
return getConfig(AGENT_STATE_KEY) || null;
|
|
134
|
+
}
|
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
export const DEFAULT_MAX_STEPS = 10;
|
|
2
|
+
|
|
3
|
+
function normalizeDecision(raw, fallbackStep) {
|
|
4
|
+
if (!raw || typeof raw !== 'object') {
|
|
5
|
+
return {
|
|
6
|
+
thought: 'No structured response returned.',
|
|
7
|
+
action: 'finish',
|
|
8
|
+
actionInput: {},
|
|
9
|
+
final: 'Agent stopped because the model did not return valid JSON.',
|
|
10
|
+
stop: true,
|
|
11
|
+
stopReason: 'invalid_response',
|
|
12
|
+
};
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
return {
|
|
16
|
+
thought: String(raw.thought || '').trim() || `Step ${fallbackStep}`,
|
|
17
|
+
action: String(raw.action || 'finish').trim(),
|
|
18
|
+
actionInput: raw.actionInput && typeof raw.actionInput === 'object' ? raw.actionInput : {},
|
|
19
|
+
final: raw.final ? String(raw.final).trim() : '',
|
|
20
|
+
stop: Boolean(raw.stop),
|
|
21
|
+
stopReason: raw.stopReason ? String(raw.stopReason).trim() : '',
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function sameAction(a, b) {
|
|
26
|
+
return a && b && a.action === b.action && JSON.stringify(a.actionInput || {}) === JSON.stringify(b.actionInput || {});
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function buildPrompt({ goal, allowActions, maxSteps, stepNumber, tools, steps }) {
|
|
30
|
+
const history = steps.length === 0
|
|
31
|
+
? 'No prior steps yet.'
|
|
32
|
+
: steps.map((step) => {
|
|
33
|
+
const bits = [
|
|
34
|
+
`Step ${step.step}`,
|
|
35
|
+
`thought=${step.thought}`,
|
|
36
|
+
`action=${step.action}`,
|
|
37
|
+
`input=${JSON.stringify(step.actionInput || {})}`,
|
|
38
|
+
step.observation ? `observation=${step.observation}` : '',
|
|
39
|
+
].filter(Boolean);
|
|
40
|
+
return bits.join('\n');
|
|
41
|
+
}).join('\n\n');
|
|
42
|
+
|
|
43
|
+
return [
|
|
44
|
+
'You are the DARKSOL agent loop controller.',
|
|
45
|
+
`Goal: ${goal}`,
|
|
46
|
+
`Current step: ${stepNumber}/${maxSteps}`,
|
|
47
|
+
`Safe mode: ${allowActions ? 'off' : 'on'}${allowActions ? '' : ' (mutating tools are blocked)'}`,
|
|
48
|
+
'Available tools:',
|
|
49
|
+
tools.map((tool) => `- ${tool.name}${tool.mutating ? ' [mutating]' : ''}: ${tool.description}`).join('\n'),
|
|
50
|
+
'Prior step log:',
|
|
51
|
+
history,
|
|
52
|
+
'Rules:',
|
|
53
|
+
'- Think briefly and act conservatively.',
|
|
54
|
+
'- Use read-only tools first unless the goal clearly requires an action and actions are allowed.',
|
|
55
|
+
'- If you have enough information, return action "finish" with a concise final answer.',
|
|
56
|
+
'- If the last tool was blocked or failed, adjust instead of repeating forever.',
|
|
57
|
+
'- Respond as JSON only.',
|
|
58
|
+
'JSON schema:',
|
|
59
|
+
'{"thought":"string","action":"tool-name|finish","actionInput":{},"final":"string","stop":false,"stopReason":"string"}',
|
|
60
|
+
].join('\n\n');
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export async function runAgentLoop({
|
|
64
|
+
goal,
|
|
65
|
+
llm,
|
|
66
|
+
tools,
|
|
67
|
+
executeTool,
|
|
68
|
+
maxSteps = DEFAULT_MAX_STEPS,
|
|
69
|
+
allowActions = false,
|
|
70
|
+
onProgress = () => {},
|
|
71
|
+
saveOutcome = async () => {},
|
|
72
|
+
persistStatus = async () => {},
|
|
73
|
+
}) {
|
|
74
|
+
const startedAt = new Date().toISOString();
|
|
75
|
+
const steps = [];
|
|
76
|
+
|
|
77
|
+
await persistStatus({
|
|
78
|
+
status: 'running',
|
|
79
|
+
goal,
|
|
80
|
+
allowActions,
|
|
81
|
+
maxSteps,
|
|
82
|
+
startedAt,
|
|
83
|
+
completedAt: null,
|
|
84
|
+
stepsTaken: 0,
|
|
85
|
+
summary: '',
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
for (let stepNumber = 1; stepNumber <= maxSteps; stepNumber += 1) {
|
|
89
|
+
const prompt = buildPrompt({ goal, allowActions, maxSteps, stepNumber, tools, steps });
|
|
90
|
+
onProgress({ type: 'step-start', step: stepNumber, maxSteps });
|
|
91
|
+
|
|
92
|
+
const response = await llm.json(prompt, {
|
|
93
|
+
ephemeral: true,
|
|
94
|
+
skipMemoryExtraction: true,
|
|
95
|
+
});
|
|
96
|
+
const decision = normalizeDecision(response.parsed, stepNumber);
|
|
97
|
+
const stepLog = {
|
|
98
|
+
step: stepNumber,
|
|
99
|
+
thought: decision.thought,
|
|
100
|
+
action: decision.action,
|
|
101
|
+
actionInput: decision.actionInput,
|
|
102
|
+
observation: '',
|
|
103
|
+
};
|
|
104
|
+
steps.push(stepLog);
|
|
105
|
+
onProgress({ type: 'thought', step: stepNumber, thought: decision.thought, action: decision.action, actionInput: decision.actionInput });
|
|
106
|
+
|
|
107
|
+
const previous = steps.length > 1 ? steps[steps.length - 2] : null;
|
|
108
|
+
const repeatedLoop = sameAction(stepLog, previous) && sameAction(previous, steps.length > 2 ? steps[steps.length - 3] : null);
|
|
109
|
+
if (repeatedLoop) {
|
|
110
|
+
const completedAt = new Date().toISOString();
|
|
111
|
+
const result = {
|
|
112
|
+
status: 'stopped',
|
|
113
|
+
goal,
|
|
114
|
+
allowActions,
|
|
115
|
+
maxSteps,
|
|
116
|
+
startedAt,
|
|
117
|
+
completedAt,
|
|
118
|
+
stepsTaken: steps.length,
|
|
119
|
+
stopReason: 'repeat_guard',
|
|
120
|
+
final: 'Agent stopped after repeating the same step three times.',
|
|
121
|
+
steps,
|
|
122
|
+
};
|
|
123
|
+
await persistStatus({ ...result, summary: result.final });
|
|
124
|
+
await saveOutcome(result);
|
|
125
|
+
return result;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
if (decision.stop || decision.action === 'finish' || decision.final) {
|
|
129
|
+
const completedAt = new Date().toISOString();
|
|
130
|
+
const final = decision.final || 'Agent stopped without a final answer.';
|
|
131
|
+
const result = {
|
|
132
|
+
status: 'completed',
|
|
133
|
+
goal,
|
|
134
|
+
allowActions,
|
|
135
|
+
maxSteps,
|
|
136
|
+
startedAt,
|
|
137
|
+
completedAt,
|
|
138
|
+
stepsTaken: steps.length,
|
|
139
|
+
stopReason: decision.stopReason || 'final',
|
|
140
|
+
final,
|
|
141
|
+
steps,
|
|
142
|
+
};
|
|
143
|
+
await persistStatus({ ...result, summary: final });
|
|
144
|
+
await saveOutcome(result);
|
|
145
|
+
onProgress({ type: 'final', final });
|
|
146
|
+
return result;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
const observation = await executeTool(decision.action, decision.actionInput || {});
|
|
150
|
+
stepLog.observation = observation.summary || observation.error || JSON.stringify(observation);
|
|
151
|
+
stepLog.result = observation;
|
|
152
|
+
onProgress({ type: 'observation', step: stepNumber, tool: decision.action, observation });
|
|
153
|
+
|
|
154
|
+
if (observation.blocked) {
|
|
155
|
+
stepLog.observation = observation.error;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
await persistStatus({
|
|
159
|
+
status: 'running',
|
|
160
|
+
goal,
|
|
161
|
+
allowActions,
|
|
162
|
+
maxSteps,
|
|
163
|
+
startedAt,
|
|
164
|
+
completedAt: null,
|
|
165
|
+
stepsTaken: steps.length,
|
|
166
|
+
lastTool: decision.action,
|
|
167
|
+
lastObservation: stepLog.observation,
|
|
168
|
+
summary: '',
|
|
169
|
+
});
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
const completedAt = new Date().toISOString();
|
|
173
|
+
const final = `Agent reached the step limit (${maxSteps}) before finishing.`;
|
|
174
|
+
const result = {
|
|
175
|
+
status: 'stopped',
|
|
176
|
+
goal,
|
|
177
|
+
allowActions,
|
|
178
|
+
maxSteps,
|
|
179
|
+
startedAt,
|
|
180
|
+
completedAt,
|
|
181
|
+
stepsTaken: steps.length,
|
|
182
|
+
stopReason: 'max_steps',
|
|
183
|
+
final,
|
|
184
|
+
steps,
|
|
185
|
+
};
|
|
186
|
+
await persistStatus({ ...result, summary: final });
|
|
187
|
+
await saveOutcome(result);
|
|
188
|
+
onProgress({ type: 'final', final });
|
|
189
|
+
return result;
|
|
190
|
+
}
|