swarmlancer-cli 0.2.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/README.md +15 -0
- package/dist/agent.d.ts +13 -0
- package/dist/agent.js +202 -0
- package/dist/app.d.ts +4 -0
- package/dist/app.js +496 -0
- package/dist/config.d.ts +49 -0
- package/dist/config.js +175 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +129 -0
- package/dist/inference.d.ts +13 -0
- package/dist/inference.js +105 -0
- package/dist/login.d.ts +1 -0
- package/dist/login.js +57 -0
- package/dist/screening.d.ts +22 -0
- package/dist/screening.js +101 -0
- package/dist/screens/agent-config.d.ts +14 -0
- package/dist/screens/agent-config.js +64 -0
- package/dist/screens/agent-editor.d.ts +13 -0
- package/dist/screens/agent-editor.js +64 -0
- package/dist/screens/agent-list.d.ts +22 -0
- package/dist/screens/agent-list.js +73 -0
- package/dist/screens/agent-picker.d.ts +15 -0
- package/dist/screens/agent-picker.js +51 -0
- package/dist/screens/agent-running.d.ts +20 -0
- package/dist/screens/agent-running.js +68 -0
- package/dist/screens/banner.d.ts +6 -0
- package/dist/screens/banner.js +27 -0
- package/dist/screens/dashboard.d.ts +16 -0
- package/dist/screens/dashboard.js +59 -0
- package/dist/screens/discovery-settings.d.ts +17 -0
- package/dist/screens/discovery-settings.js +189 -0
- package/dist/screens/message.d.ts +15 -0
- package/dist/screens/message.js +39 -0
- package/dist/screens/model-picker.d.ts +14 -0
- package/dist/screens/model-picker.js +49 -0
- package/dist/screens/name-editor.d.ts +15 -0
- package/dist/screens/name-editor.js +67 -0
- package/dist/screens/session-goal.d.ts +13 -0
- package/dist/screens/session-goal.js +61 -0
- package/dist/screens/settings.d.ts +15 -0
- package/dist/screens/settings.js +126 -0
- package/dist/screens/setup-wizard.d.ts +20 -0
- package/dist/screens/setup-wizard.js +120 -0
- package/dist/screens/status-panel.d.ts +15 -0
- package/dist/screens/status-panel.js +37 -0
- package/dist/theme.d.ts +42 -0
- package/dist/theme.js +56 -0
- package/package.json +49 -0
package/README.md
ADDED
package/dist/agent.d.ts
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { type AgentLimits } from "./config.js";
|
|
2
|
+
type LogFn = (line: string) => void;
|
|
3
|
+
/**
|
|
4
|
+
* Start the WebSocket agent connection.
|
|
5
|
+
* @param limits - Agent limits to enforce (from the selected agent profile)
|
|
6
|
+
* @param agentId - The local agent UUID
|
|
7
|
+
* @param agentName - The human-readable agent name
|
|
8
|
+
* @param log - Logging function
|
|
9
|
+
*/
|
|
10
|
+
export declare function startAgent(limits?: AgentLimits, agentId?: string, agentName?: string, log?: LogFn): void;
|
|
11
|
+
export declare function sendToServer(msg: Record<string, unknown>): void;
|
|
12
|
+
export declare function stopAgent(): void;
|
|
13
|
+
export {};
|
package/dist/agent.js
ADDED
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
import WebSocket from "ws";
|
|
2
|
+
import { getConfig, DEFAULT_LIMITS } from "./config.js";
|
|
3
|
+
import { runInference } from "./inference.js";
|
|
4
|
+
import { colors } from "./theme.js";
|
|
5
|
+
let activeWs = null;
|
|
6
|
+
let alive = false;
|
|
7
|
+
let reconnectTimer = null;
|
|
8
|
+
let idleTimer = null;
|
|
9
|
+
// Session counters
|
|
10
|
+
let activeConversations = new Set();
|
|
11
|
+
let conversationMessageCounts = new Map();
|
|
12
|
+
let totalConversations = 0;
|
|
13
|
+
let lastConversationTime = 0;
|
|
14
|
+
function resetCounters() {
|
|
15
|
+
activeConversations.clear();
|
|
16
|
+
conversationMessageCounts.clear();
|
|
17
|
+
totalConversations = 0;
|
|
18
|
+
lastConversationTime = 0;
|
|
19
|
+
}
|
|
20
|
+
function resetIdleTimer(limits, log) {
|
|
21
|
+
if (idleTimer)
|
|
22
|
+
clearTimeout(idleTimer);
|
|
23
|
+
idleTimer = setTimeout(() => {
|
|
24
|
+
log(colors.yellow(`⏱ Auto-stopping: idle for ${limits.autoStopIdleMinutes} minutes`));
|
|
25
|
+
stopAgent();
|
|
26
|
+
}, limits.autoStopIdleMinutes * 60 * 1000);
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Start the WebSocket agent connection.
|
|
30
|
+
* @param limits - Agent limits to enforce (from the selected agent profile)
|
|
31
|
+
* @param agentId - The local agent UUID
|
|
32
|
+
* @param agentName - The human-readable agent name
|
|
33
|
+
* @param log - Logging function
|
|
34
|
+
*/
|
|
35
|
+
export function startAgent(limits = DEFAULT_LIMITS, agentId, agentName, log = console.log) {
|
|
36
|
+
const config = getConfig();
|
|
37
|
+
if (!config.token) {
|
|
38
|
+
log(colors.red("Not logged in. Run: swarmlancer login"));
|
|
39
|
+
return;
|
|
40
|
+
}
|
|
41
|
+
resetCounters();
|
|
42
|
+
const wsUrl = config.serverUrl.replace(/^http/, "ws") + "/ws";
|
|
43
|
+
alive = true;
|
|
44
|
+
// Log limits
|
|
45
|
+
log(colors.gray(`Limits: ${limits.maxConcurrentConversations} concurrent, ${limits.maxMessagesPerConversation} msgs/convo, ${limits.cooldownSeconds}s cooldown, ${limits.maxConversationsPerSession} max/session, ${limits.autoStopIdleMinutes}m idle`));
|
|
46
|
+
function connect() {
|
|
47
|
+
log(colors.cyan(`Connecting to ${wsUrl}...`));
|
|
48
|
+
const ws = new WebSocket(wsUrl);
|
|
49
|
+
activeWs = ws;
|
|
50
|
+
// Start idle timer
|
|
51
|
+
resetIdleTimer(limits, log);
|
|
52
|
+
ws.on("open", () => {
|
|
53
|
+
ws.send(JSON.stringify({ type: "auth", token: config.token, agentId, agentName }));
|
|
54
|
+
});
|
|
55
|
+
ws.on("message", async (raw) => {
|
|
56
|
+
const msg = JSON.parse(raw.toString());
|
|
57
|
+
switch (msg.type) {
|
|
58
|
+
case "authenticated":
|
|
59
|
+
log(colors.green("Agent online"));
|
|
60
|
+
log("");
|
|
61
|
+
log(colors.gray("Waiting for conversations..."));
|
|
62
|
+
ws.send(JSON.stringify({ type: "get_online_users" }));
|
|
63
|
+
break;
|
|
64
|
+
case "online_users": {
|
|
65
|
+
const users = msg.users;
|
|
66
|
+
if (users.length > 0) {
|
|
67
|
+
log(colors.cyan(`📡 Online: ${users.map((u) => `${u.displayName} (@${u.githubUsername})`).join(", ")}`));
|
|
68
|
+
}
|
|
69
|
+
else {
|
|
70
|
+
log(colors.gray("No other agents online right now."));
|
|
71
|
+
}
|
|
72
|
+
break;
|
|
73
|
+
}
|
|
74
|
+
case "inference_request": {
|
|
75
|
+
const { requestId, conversationId, systemPrompt, messages } = msg;
|
|
76
|
+
// Reset idle timer on activity
|
|
77
|
+
resetIdleTimer(limits, log);
|
|
78
|
+
// Check concurrent conversation limit
|
|
79
|
+
if (!activeConversations.has(conversationId) &&
|
|
80
|
+
activeConversations.size >= limits.maxConcurrentConversations) {
|
|
81
|
+
log(colors.yellow(`⚠ Skipping: ${limits.maxConcurrentConversations} concurrent conversations active`));
|
|
82
|
+
ws.send(JSON.stringify({
|
|
83
|
+
type: "inference_error",
|
|
84
|
+
requestId,
|
|
85
|
+
error: "Agent is at max concurrent conversations. Try again later.",
|
|
86
|
+
}));
|
|
87
|
+
break;
|
|
88
|
+
}
|
|
89
|
+
// Check session total limit
|
|
90
|
+
if (!activeConversations.has(conversationId) &&
|
|
91
|
+
totalConversations >= limits.maxConversationsPerSession) {
|
|
92
|
+
log(colors.yellow(`⚠ Session limit reached (${limits.maxConversationsPerSession} conversations). Stopping.`));
|
|
93
|
+
stopAgent();
|
|
94
|
+
break;
|
|
95
|
+
}
|
|
96
|
+
// Check cooldown
|
|
97
|
+
if (!activeConversations.has(conversationId) &&
|
|
98
|
+
limits.cooldownSeconds > 0) {
|
|
99
|
+
const elapsed = (Date.now() - lastConversationTime) / 1000;
|
|
100
|
+
if (lastConversationTime > 0 && elapsed < limits.cooldownSeconds) {
|
|
101
|
+
const wait = Math.ceil(limits.cooldownSeconds - elapsed);
|
|
102
|
+
log(colors.yellow(`⚠ Cooldown: ${wait}s remaining`));
|
|
103
|
+
ws.send(JSON.stringify({
|
|
104
|
+
type: "inference_error",
|
|
105
|
+
requestId,
|
|
106
|
+
error: `Agent is in cooldown. Try again in ${wait} seconds.`,
|
|
107
|
+
}));
|
|
108
|
+
break;
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
// Track conversation
|
|
112
|
+
if (!activeConversations.has(conversationId)) {
|
|
113
|
+
activeConversations.add(conversationId);
|
|
114
|
+
conversationMessageCounts.set(conversationId, 0);
|
|
115
|
+
totalConversations++;
|
|
116
|
+
lastConversationTime = Date.now();
|
|
117
|
+
}
|
|
118
|
+
// Check per-conversation message limit
|
|
119
|
+
const msgCount = (conversationMessageCounts.get(conversationId) ?? 0) + 1;
|
|
120
|
+
conversationMessageCounts.set(conversationId, msgCount);
|
|
121
|
+
if (msgCount > limits.maxMessagesPerConversation) {
|
|
122
|
+
log(colors.yellow(`⚠ Conversation ${conversationId.slice(0, 8)} hit ${limits.maxMessagesPerConversation} message limit`));
|
|
123
|
+
activeConversations.delete(conversationId);
|
|
124
|
+
ws.send(JSON.stringify({
|
|
125
|
+
type: "inference_response",
|
|
126
|
+
requestId,
|
|
127
|
+
content: "I've reached my message limit for this conversation. It was great talking! Feel free to connect on GitHub if you'd like to continue.",
|
|
128
|
+
}));
|
|
129
|
+
break;
|
|
130
|
+
}
|
|
131
|
+
log(colors.yellow(`💬 [${conversationId.slice(0, 8)}] msg ${msgCount}/${limits.maxMessagesPerConversation} (${activeConversations.size}/${limits.maxConcurrentConversations} active, ${totalConversations}/${limits.maxConversationsPerSession} total)`));
|
|
132
|
+
try {
|
|
133
|
+
let response = await runInference(systemPrompt, messages);
|
|
134
|
+
// Enforce response length limit
|
|
135
|
+
if (response.length > limits.maxResponseLength) {
|
|
136
|
+
response = response.slice(0, limits.maxResponseLength);
|
|
137
|
+
log(colors.gray(` (truncated to ${limits.maxResponseLength} chars)`));
|
|
138
|
+
}
|
|
139
|
+
const preview = response.length > 80 ? response.slice(0, 80) + "..." : response;
|
|
140
|
+
log(colors.green(`✓ Response: ${preview}`));
|
|
141
|
+
ws.send(JSON.stringify({
|
|
142
|
+
type: "inference_response",
|
|
143
|
+
requestId,
|
|
144
|
+
content: response,
|
|
145
|
+
}));
|
|
146
|
+
}
|
|
147
|
+
catch (err) {
|
|
148
|
+
const errorMsg = err instanceof Error ? err.message : "Unknown error";
|
|
149
|
+
log(colors.red(`✗ Inference failed: ${errorMsg}`));
|
|
150
|
+
ws.send(JSON.stringify({
|
|
151
|
+
type: "inference_error",
|
|
152
|
+
requestId,
|
|
153
|
+
error: errorMsg,
|
|
154
|
+
}));
|
|
155
|
+
}
|
|
156
|
+
break;
|
|
157
|
+
}
|
|
158
|
+
case "conversation_started": {
|
|
159
|
+
const { conversationId, withUser } = msg;
|
|
160
|
+
log(colors.brightGreen(`🤝 Conversation started with ${withUser.displayName} (@${withUser.githubUsername})`));
|
|
161
|
+
resetIdleTimer(limits, log);
|
|
162
|
+
break;
|
|
163
|
+
}
|
|
164
|
+
case "conversation_ended": {
|
|
165
|
+
const endedId = msg.conversationId;
|
|
166
|
+
activeConversations.delete(endedId);
|
|
167
|
+
conversationMessageCounts.delete(endedId);
|
|
168
|
+
log(colors.gray(` Conversation ${endedId?.slice(0, 8)} ended (${activeConversations.size} active)`));
|
|
169
|
+
break;
|
|
170
|
+
}
|
|
171
|
+
case "error":
|
|
172
|
+
log(colors.red(`Server error: ${msg.message}`));
|
|
173
|
+
break;
|
|
174
|
+
}
|
|
175
|
+
});
|
|
176
|
+
ws.on("close", () => {
|
|
177
|
+
if (alive) {
|
|
178
|
+
log(colors.yellow("Disconnected. Reconnecting in 5s..."));
|
|
179
|
+
reconnectTimer = setTimeout(connect, 5000);
|
|
180
|
+
}
|
|
181
|
+
});
|
|
182
|
+
ws.on("error", (err) => {
|
|
183
|
+
log(colors.red(`WebSocket error: ${err.message}`));
|
|
184
|
+
});
|
|
185
|
+
}
|
|
186
|
+
connect();
|
|
187
|
+
}
|
|
188
|
+
export function sendToServer(msg) {
|
|
189
|
+
if (activeWs && activeWs.readyState === WebSocket.OPEN) {
|
|
190
|
+
activeWs.send(JSON.stringify(msg));
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
export function stopAgent() {
|
|
194
|
+
alive = false;
|
|
195
|
+
if (reconnectTimer)
|
|
196
|
+
clearTimeout(reconnectTimer);
|
|
197
|
+
if (idleTimer)
|
|
198
|
+
clearTimeout(idleTimer);
|
|
199
|
+
activeWs?.close();
|
|
200
|
+
activeWs = null;
|
|
201
|
+
resetCounters();
|
|
202
|
+
}
|
package/dist/app.d.ts
ADDED