@ebowwa/glm-daemon 0.3.2 → 0.4.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/package.json +5 -5
- package/src/agent.js +166 -0
- package/src/builtin-tools.js +349 -0
- package/src/channels/base.js +509 -0
- package/src/channels/discord.js +374 -0
- package/src/channels/index.js +186 -0
- package/src/channels/telegram.js +395 -0
- package/src/daemon.js +593 -0
- package/src/daemon.ts +188 -1
- package/src/hooks.js +145 -0
- package/src/hooks.ts +3 -0
- package/src/index.js +123 -0
- package/src/memory.js +323 -0
- package/src/state.js +168 -0
- package/src/tools.js +192 -0
- package/src/types.js +21 -0
- package/src/types.ts +62 -0
- package/src/worktree.js +195 -0
package/src/memory.js
ADDED
|
@@ -0,0 +1,323 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Conversation Memory - Generic conversation tracking for any channel
|
|
4
|
+
*
|
|
5
|
+
* Tracks chat history per user/conversation with persistence.
|
|
6
|
+
* Can be used by Telegram, Discord, or any other channel.
|
|
7
|
+
*
|
|
8
|
+
* Storage format: JSONL (JSON Lines)
|
|
9
|
+
* Each line is a JSON object: {"id": "...", "messages": [...], "lastUpdated": timestamp}
|
|
10
|
+
*/
|
|
11
|
+
var __extends = (this && this.__extends) || (function () {
|
|
12
|
+
var extendStatics = function (d, b) {
|
|
13
|
+
extendStatics = Object.setPrototypeOf ||
|
|
14
|
+
({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) ||
|
|
15
|
+
function (d, b) { for (var p in b) if (Object.prototype.hasOwnProperty.call(b, p)) d[p] = b[p]; };
|
|
16
|
+
return extendStatics(d, b);
|
|
17
|
+
};
|
|
18
|
+
return function (d, b) {
|
|
19
|
+
if (typeof b !== "function" && b !== null)
|
|
20
|
+
throw new TypeError("Class extends value " + String(b) + " is not a constructor or null");
|
|
21
|
+
extendStatics(d, b);
|
|
22
|
+
function __() { this.constructor = d; }
|
|
23
|
+
d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
|
|
24
|
+
};
|
|
25
|
+
})();
|
|
26
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
27
|
+
exports.StringConversationMemory = exports.NumericConversationMemory = exports.ConversationMemory = void 0;
|
|
28
|
+
var fs_1 = require("fs");
|
|
29
|
+
var path_1 = require("path");
|
|
30
|
+
/**
|
|
31
|
+
* Generic conversation memory that tracks chat history per conversation ID.
|
|
32
|
+
*
|
|
33
|
+
* Uses JSONL format for efficient append-only writes and streaming reads.
|
|
34
|
+
* Automatically migrates from old JSON format on first load.
|
|
35
|
+
*
|
|
36
|
+
* The ID type is generic (string | number) to support different channel types:
|
|
37
|
+
* - Telegram: number (chatId)
|
|
38
|
+
* - Discord: string (channelId/userId)
|
|
39
|
+
* - Slack: string (channelId)
|
|
40
|
+
*
|
|
41
|
+
* @example
|
|
42
|
+
* ```ts
|
|
43
|
+
* const memory = new ConversationMemory({ maxMessages: 20 });
|
|
44
|
+
* memory.add(12345, 'user', 'Hello!');
|
|
45
|
+
* memory.add(12345, 'assistant', 'Hi there!');
|
|
46
|
+
* const history = memory.get(12345);
|
|
47
|
+
* ```
|
|
48
|
+
*/
|
|
49
|
+
var ConversationMemory = /** @class */ (function () {
|
|
50
|
+
function ConversationMemory(config) {
|
|
51
|
+
if (config === void 0) { config = {}; }
|
|
52
|
+
var _a, _b;
|
|
53
|
+
this.conversations = new Map();
|
|
54
|
+
this.needsFullSave = false;
|
|
55
|
+
// Default to .jsonl extension
|
|
56
|
+
var defaultFile = "./conversations.jsonl";
|
|
57
|
+
this.file = config.file || defaultFile;
|
|
58
|
+
// Auto-migrate from .json to .jsonl if needed
|
|
59
|
+
if (config.autoMigrate !== false && !(0, fs_1.existsSync)(this.file)) {
|
|
60
|
+
this.migrateFromJson(this.file);
|
|
61
|
+
}
|
|
62
|
+
this.maxMessages = (_a = config.maxMessages) !== null && _a !== void 0 ? _a : 10;
|
|
63
|
+
this.includeTimestamps = (_b = config.includeTimestamps) !== null && _b !== void 0 ? _b : true;
|
|
64
|
+
this.load();
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* Migrate from old JSON format to JSONL
|
|
68
|
+
*/
|
|
69
|
+
ConversationMemory.prototype.migrateFromJson = function (jsonlFile) {
|
|
70
|
+
// Check for old .json file
|
|
71
|
+
var jsonFile = jsonlFile.replace(/\.jsonl$/, ".json");
|
|
72
|
+
if (!(0, fs_1.existsSync)(jsonFile))
|
|
73
|
+
return;
|
|
74
|
+
try {
|
|
75
|
+
var data = JSON.parse((0, fs_1.readFileSync)(jsonFile, "utf-8"));
|
|
76
|
+
var entries = [];
|
|
77
|
+
for (var _i = 0, _a = Object.entries(data); _i < _a.length; _i++) {
|
|
78
|
+
var _b = _a[_i], id = _b[0], messages = _b[1];
|
|
79
|
+
entries.push({
|
|
80
|
+
id: id,
|
|
81
|
+
messages: messages,
|
|
82
|
+
lastUpdated: Date.now(),
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
// Write as JSONL
|
|
86
|
+
var dir = (0, path_1.dirname)(jsonlFile);
|
|
87
|
+
if (!(0, fs_1.existsSync)(dir)) {
|
|
88
|
+
(0, fs_1.mkdirSync)(dir, { recursive: true });
|
|
89
|
+
}
|
|
90
|
+
var lines = entries.map(function (e) { return JSON.stringify(e); }).join("\n");
|
|
91
|
+
(0, fs_1.writeFileSync)(jsonlFile, lines + (lines ? "\n" : ""));
|
|
92
|
+
// Backup old file instead of deleting
|
|
93
|
+
var backupFile = jsonFile + ".backup";
|
|
94
|
+
(0, fs_1.renameSync)(jsonFile, backupFile);
|
|
95
|
+
console.log("Migrated ".concat(jsonFile, " to ").concat(jsonlFile, " (").concat(entries.length, " conversations)"));
|
|
96
|
+
}
|
|
97
|
+
catch (error) {
|
|
98
|
+
console.error("Migration failed: ".concat(error));
|
|
99
|
+
}
|
|
100
|
+
};
|
|
101
|
+
/**
|
|
102
|
+
* Load conversations from JSONL file
|
|
103
|
+
* Uses synchronous read for constructor compatibility
|
|
104
|
+
*/
|
|
105
|
+
ConversationMemory.prototype.load = function () {
|
|
106
|
+
if (!(0, fs_1.existsSync)(this.file))
|
|
107
|
+
return;
|
|
108
|
+
try {
|
|
109
|
+
var content = (0, fs_1.readFileSync)(this.file, "utf-8");
|
|
110
|
+
var lines = content.split("\n");
|
|
111
|
+
for (var _i = 0, lines_1 = lines; _i < lines_1.length; _i++) {
|
|
112
|
+
var line = lines_1[_i];
|
|
113
|
+
if (!line.trim())
|
|
114
|
+
continue;
|
|
115
|
+
try {
|
|
116
|
+
var entry = JSON.parse(line);
|
|
117
|
+
var key = this.parseKey(entry.id);
|
|
118
|
+
this.conversations.set(key, entry.messages);
|
|
119
|
+
}
|
|
120
|
+
catch (_a) {
|
|
121
|
+
// Skip malformed lines
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
catch (_b) {
|
|
126
|
+
// Start fresh on error
|
|
127
|
+
}
|
|
128
|
+
};
|
|
129
|
+
/**
|
|
130
|
+
* Parse string key back to original type
|
|
131
|
+
*/
|
|
132
|
+
ConversationMemory.prototype.parseKey = function (id) {
|
|
133
|
+
// Try to parse as number if it looks like one
|
|
134
|
+
if (/^-?\d+$/.test(id)) {
|
|
135
|
+
return Number(id);
|
|
136
|
+
}
|
|
137
|
+
return id;
|
|
138
|
+
};
|
|
139
|
+
/**
|
|
140
|
+
* Convert IdType to string for storage
|
|
141
|
+
*/
|
|
142
|
+
ConversationMemory.prototype.stringifyKey = function (id) {
|
|
143
|
+
return String(id);
|
|
144
|
+
};
|
|
145
|
+
/**
|
|
146
|
+
* Save all conversations to disk (full rewrite)
|
|
147
|
+
* Used when entries are deleted or modified
|
|
148
|
+
*/
|
|
149
|
+
ConversationMemory.prototype.saveFull = function () {
|
|
150
|
+
try {
|
|
151
|
+
var dir = (0, path_1.dirname)(this.file);
|
|
152
|
+
if (!(0, fs_1.existsSync)(dir)) {
|
|
153
|
+
(0, fs_1.mkdirSync)(dir, { recursive: true });
|
|
154
|
+
}
|
|
155
|
+
var lines = [];
|
|
156
|
+
for (var _i = 0, _a = this.conversations; _i < _a.length; _i++) {
|
|
157
|
+
var _b = _a[_i], id = _b[0], messages = _b[1];
|
|
158
|
+
var entry = {
|
|
159
|
+
id: this.stringifyKey(id),
|
|
160
|
+
messages: messages,
|
|
161
|
+
lastUpdated: Date.now(),
|
|
162
|
+
};
|
|
163
|
+
lines.push(JSON.stringify(entry));
|
|
164
|
+
}
|
|
165
|
+
(0, fs_1.writeFileSync)(this.file, lines.join("\n") + (lines.length ? "\n" : ""));
|
|
166
|
+
this.needsFullSave = false;
|
|
167
|
+
}
|
|
168
|
+
catch (error) {
|
|
169
|
+
console.error("Save failed: ".concat(error));
|
|
170
|
+
}
|
|
171
|
+
};
|
|
172
|
+
/**
|
|
173
|
+
* Append a single entry to the file (efficient for adds)
|
|
174
|
+
*/
|
|
175
|
+
ConversationMemory.prototype.appendEntry = function (conversationId, messages) {
|
|
176
|
+
try {
|
|
177
|
+
var dir = (0, path_1.dirname)(this.file);
|
|
178
|
+
if (!(0, fs_1.existsSync)(dir)) {
|
|
179
|
+
(0, fs_1.mkdirSync)(dir, { recursive: true });
|
|
180
|
+
}
|
|
181
|
+
var entry = {
|
|
182
|
+
id: this.stringifyKey(conversationId),
|
|
183
|
+
messages: messages,
|
|
184
|
+
lastUpdated: Date.now(),
|
|
185
|
+
};
|
|
186
|
+
// Check if file exists and has content to determine if we need newline
|
|
187
|
+
var hasContent = (0, fs_1.existsSync)(this.file) && (0, fs_1.readFileSync)(this.file, "utf-8").length > 0;
|
|
188
|
+
var prefix = hasContent ? "" : "";
|
|
189
|
+
var line = JSON.stringify(entry) + "\n";
|
|
190
|
+
// We need to do a full save to replace the old entry for this conversation
|
|
191
|
+
// JSONL doesn't support in-place updates, so we mark for full save
|
|
192
|
+
this.needsFullSave = true;
|
|
193
|
+
// For now, do a full save since we need to replace the entry
|
|
194
|
+
this.saveFull();
|
|
195
|
+
}
|
|
196
|
+
catch (error) {
|
|
197
|
+
console.error("Append failed: ".concat(error));
|
|
198
|
+
}
|
|
199
|
+
};
|
|
200
|
+
/**
|
|
201
|
+
* Add a message to a conversation
|
|
202
|
+
*/
|
|
203
|
+
ConversationMemory.prototype.add = function (conversationId, role, content) {
|
|
204
|
+
if (!this.conversations.has(conversationId)) {
|
|
205
|
+
this.conversations.set(conversationId, []);
|
|
206
|
+
}
|
|
207
|
+
var history = this.conversations.get(conversationId);
|
|
208
|
+
var message = { role: role, content: content };
|
|
209
|
+
if (this.includeTimestamps) {
|
|
210
|
+
message.timestamp = Date.now();
|
|
211
|
+
}
|
|
212
|
+
history.push(message);
|
|
213
|
+
// Keep only last N messages
|
|
214
|
+
if (history.length > this.maxMessages) {
|
|
215
|
+
history.splice(0, history.length - this.maxMessages);
|
|
216
|
+
}
|
|
217
|
+
// Save the updated conversation
|
|
218
|
+
this.appendEntry(conversationId, history);
|
|
219
|
+
};
|
|
220
|
+
/**
|
|
221
|
+
* Get conversation history for a specific conversation
|
|
222
|
+
*/
|
|
223
|
+
ConversationMemory.prototype.get = function (conversationId) {
|
|
224
|
+
return this.conversations.get(conversationId) || [];
|
|
225
|
+
};
|
|
226
|
+
/**
|
|
227
|
+
* Get all conversations
|
|
228
|
+
*/
|
|
229
|
+
ConversationMemory.prototype.getAll = function () {
|
|
230
|
+
return new Map(this.conversations);
|
|
231
|
+
};
|
|
232
|
+
/**
|
|
233
|
+
* Clear a specific conversation
|
|
234
|
+
*/
|
|
235
|
+
ConversationMemory.prototype.clear = function (conversationId) {
|
|
236
|
+
this.conversations.delete(conversationId);
|
|
237
|
+
this.saveFull();
|
|
238
|
+
};
|
|
239
|
+
/**
|
|
240
|
+
* Clear all conversations
|
|
241
|
+
*/
|
|
242
|
+
ConversationMemory.prototype.clearAll = function () {
|
|
243
|
+
this.conversations.clear();
|
|
244
|
+
this.saveFull();
|
|
245
|
+
};
|
|
246
|
+
/**
|
|
247
|
+
* Check if a conversation exists
|
|
248
|
+
*/
|
|
249
|
+
ConversationMemory.prototype.has = function (conversationId) {
|
|
250
|
+
return this.conversations.has(conversationId);
|
|
251
|
+
};
|
|
252
|
+
/**
|
|
253
|
+
* Get the number of conversations
|
|
254
|
+
*/
|
|
255
|
+
ConversationMemory.prototype.size = function () {
|
|
256
|
+
return this.conversations.size;
|
|
257
|
+
};
|
|
258
|
+
/**
|
|
259
|
+
* Get the messages count for a specific conversation
|
|
260
|
+
*/
|
|
261
|
+
ConversationMemory.prototype.messageCount = function (conversationId) {
|
|
262
|
+
var _a;
|
|
263
|
+
return ((_a = this.conversations.get(conversationId)) === null || _a === void 0 ? void 0 : _a.length) || 0;
|
|
264
|
+
};
|
|
265
|
+
/**
|
|
266
|
+
* Get formatted history for API calls (OpenAI/Anthropic format)
|
|
267
|
+
*/
|
|
268
|
+
ConversationMemory.prototype.getForAPI = function (conversationId) {
|
|
269
|
+
return this.get(conversationId).map(function (_a) {
|
|
270
|
+
var role = _a.role, content = _a.content;
|
|
271
|
+
return ({
|
|
272
|
+
role: role,
|
|
273
|
+
content: content,
|
|
274
|
+
});
|
|
275
|
+
});
|
|
276
|
+
};
|
|
277
|
+
/**
|
|
278
|
+
* Force a full save (useful before shutdown)
|
|
279
|
+
*/
|
|
280
|
+
ConversationMemory.prototype.flush = function () {
|
|
281
|
+
if (this.needsFullSave || this.conversations.size > 0) {
|
|
282
|
+
this.saveFull();
|
|
283
|
+
}
|
|
284
|
+
};
|
|
285
|
+
/**
|
|
286
|
+
* Get the file path being used
|
|
287
|
+
*/
|
|
288
|
+
ConversationMemory.prototype.getFilePath = function () {
|
|
289
|
+
return this.file;
|
|
290
|
+
};
|
|
291
|
+
/**
|
|
292
|
+
* Check if the storage uses JSONL format
|
|
293
|
+
*/
|
|
294
|
+
ConversationMemory.prototype.isJsonl = function () {
|
|
295
|
+
return this.file.endsWith(".jsonl");
|
|
296
|
+
};
|
|
297
|
+
return ConversationMemory;
|
|
298
|
+
}());
|
|
299
|
+
exports.ConversationMemory = ConversationMemory;
|
|
300
|
+
/**
|
|
301
|
+
* Pre-configured memory for numeric IDs (like Telegram chat IDs)
|
|
302
|
+
*/
|
|
303
|
+
var NumericConversationMemory = /** @class */ (function (_super) {
|
|
304
|
+
__extends(NumericConversationMemory, _super);
|
|
305
|
+
function NumericConversationMemory(config) {
|
|
306
|
+
if (config === void 0) { config = {}; }
|
|
307
|
+
return _super.call(this, config) || this;
|
|
308
|
+
}
|
|
309
|
+
return NumericConversationMemory;
|
|
310
|
+
}(ConversationMemory));
|
|
311
|
+
exports.NumericConversationMemory = NumericConversationMemory;
|
|
312
|
+
/**
|
|
313
|
+
* Pre-configured memory for string IDs (like Discord/Slack channel IDs)
|
|
314
|
+
*/
|
|
315
|
+
var StringConversationMemory = /** @class */ (function (_super) {
|
|
316
|
+
__extends(StringConversationMemory, _super);
|
|
317
|
+
function StringConversationMemory(config) {
|
|
318
|
+
if (config === void 0) { config = {}; }
|
|
319
|
+
return _super.call(this, config) || this;
|
|
320
|
+
}
|
|
321
|
+
return StringConversationMemory;
|
|
322
|
+
}(ConversationMemory));
|
|
323
|
+
exports.StringConversationMemory = StringConversationMemory;
|
package/src/state.js
ADDED
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* GLM Daemon - State Manager
|
|
4
|
+
*
|
|
5
|
+
* Manages SLAM state persistence to disk.
|
|
6
|
+
*/
|
|
7
|
+
var __assign = (this && this.__assign) || function () {
|
|
8
|
+
__assign = Object.assign || function(t) {
|
|
9
|
+
for (var s, i = 1, n = arguments.length; i < n; i++) {
|
|
10
|
+
s = arguments[i];
|
|
11
|
+
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p))
|
|
12
|
+
t[p] = s[p];
|
|
13
|
+
}
|
|
14
|
+
return t;
|
|
15
|
+
};
|
|
16
|
+
return __assign.apply(this, arguments);
|
|
17
|
+
};
|
|
18
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
19
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
20
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
21
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
22
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
23
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
24
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
25
|
+
});
|
|
26
|
+
};
|
|
27
|
+
var __generator = (this && this.__generator) || function (thisArg, body) {
|
|
28
|
+
var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g = Object.create((typeof Iterator === "function" ? Iterator : Object).prototype);
|
|
29
|
+
return g.next = verb(0), g["throw"] = verb(1), g["return"] = verb(2), typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g;
|
|
30
|
+
function verb(n) { return function (v) { return step([n, v]); }; }
|
|
31
|
+
function step(op) {
|
|
32
|
+
if (f) throw new TypeError("Generator is already executing.");
|
|
33
|
+
while (g && (g = 0, op[0] && (_ = 0)), _) try {
|
|
34
|
+
if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t;
|
|
35
|
+
if (y = 0, t) op = [op[0] & 2, t.value];
|
|
36
|
+
switch (op[0]) {
|
|
37
|
+
case 0: case 1: t = op; break;
|
|
38
|
+
case 4: _.label++; return { value: op[1], done: false };
|
|
39
|
+
case 5: _.label++; y = op[1]; op = [0]; continue;
|
|
40
|
+
case 7: op = _.ops.pop(); _.trys.pop(); continue;
|
|
41
|
+
default:
|
|
42
|
+
if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; }
|
|
43
|
+
if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; }
|
|
44
|
+
if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; }
|
|
45
|
+
if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; }
|
|
46
|
+
if (t[2]) _.ops.pop();
|
|
47
|
+
_.trys.pop(); continue;
|
|
48
|
+
}
|
|
49
|
+
op = body.call(thisArg, _);
|
|
50
|
+
} catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; }
|
|
51
|
+
if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true };
|
|
52
|
+
}
|
|
53
|
+
};
|
|
54
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
55
|
+
exports.StateManager = void 0;
|
|
56
|
+
var fs_1 = require("fs");
|
|
57
|
+
var StateManager = /** @class */ (function () {
|
|
58
|
+
function StateManager(statePath) {
|
|
59
|
+
this.statePath = statePath;
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* Load state from disk
|
|
63
|
+
*/
|
|
64
|
+
StateManager.prototype.load = function () {
|
|
65
|
+
return __awaiter(this, void 0, void 0, function () {
|
|
66
|
+
var content, _a;
|
|
67
|
+
return __generator(this, function (_b) {
|
|
68
|
+
switch (_b.label) {
|
|
69
|
+
case 0:
|
|
70
|
+
_b.trys.push([0, 2, , 3]);
|
|
71
|
+
return [4 /*yield*/, fs_1.promises.readFile(this.statePath, "utf-8")];
|
|
72
|
+
case 1:
|
|
73
|
+
content = _b.sent();
|
|
74
|
+
return [2 /*return*/, JSON.parse(content)];
|
|
75
|
+
case 2:
|
|
76
|
+
_a = _b.sent();
|
|
77
|
+
// File doesn't exist or invalid JSON
|
|
78
|
+
return [2 /*return*/, null];
|
|
79
|
+
case 3: return [2 /*return*/];
|
|
80
|
+
}
|
|
81
|
+
});
|
|
82
|
+
});
|
|
83
|
+
};
|
|
84
|
+
/**
|
|
85
|
+
* Save state to disk
|
|
86
|
+
*/
|
|
87
|
+
StateManager.prototype.save = function (state) {
|
|
88
|
+
return __awaiter(this, void 0, void 0, function () {
|
|
89
|
+
return __generator(this, function (_a) {
|
|
90
|
+
switch (_a.label) {
|
|
91
|
+
case 0: return [4 /*yield*/, fs_1.promises.writeFile(this.statePath, JSON.stringify(state, null, 2), "utf-8")];
|
|
92
|
+
case 1:
|
|
93
|
+
_a.sent();
|
|
94
|
+
return [2 /*return*/];
|
|
95
|
+
}
|
|
96
|
+
});
|
|
97
|
+
});
|
|
98
|
+
};
|
|
99
|
+
/**
|
|
100
|
+
* Delete state file
|
|
101
|
+
*/
|
|
102
|
+
StateManager.prototype.delete = function () {
|
|
103
|
+
return __awaiter(this, void 0, void 0, function () {
|
|
104
|
+
var _a;
|
|
105
|
+
return __generator(this, function (_b) {
|
|
106
|
+
switch (_b.label) {
|
|
107
|
+
case 0:
|
|
108
|
+
_b.trys.push([0, 2, , 3]);
|
|
109
|
+
return [4 /*yield*/, fs_1.promises.unlink(this.statePath)];
|
|
110
|
+
case 1:
|
|
111
|
+
_b.sent();
|
|
112
|
+
return [3 /*break*/, 3];
|
|
113
|
+
case 2:
|
|
114
|
+
_a = _b.sent();
|
|
115
|
+
return [3 /*break*/, 3];
|
|
116
|
+
case 3: return [2 /*return*/];
|
|
117
|
+
}
|
|
118
|
+
});
|
|
119
|
+
});
|
|
120
|
+
};
|
|
121
|
+
/**
|
|
122
|
+
* Check if state file exists
|
|
123
|
+
*/
|
|
124
|
+
StateManager.prototype.exists = function () {
|
|
125
|
+
return __awaiter(this, void 0, void 0, function () {
|
|
126
|
+
var _a;
|
|
127
|
+
return __generator(this, function (_b) {
|
|
128
|
+
switch (_b.label) {
|
|
129
|
+
case 0:
|
|
130
|
+
_b.trys.push([0, 2, , 3]);
|
|
131
|
+
return [4 /*yield*/, fs_1.promises.access(this.statePath)];
|
|
132
|
+
case 1:
|
|
133
|
+
_b.sent();
|
|
134
|
+
return [2 /*return*/, true];
|
|
135
|
+
case 2:
|
|
136
|
+
_a = _b.sent();
|
|
137
|
+
return [2 /*return*/, false];
|
|
138
|
+
case 3: return [2 /*return*/];
|
|
139
|
+
}
|
|
140
|
+
});
|
|
141
|
+
});
|
|
142
|
+
};
|
|
143
|
+
/**
|
|
144
|
+
* Update a portion of state (merge)
|
|
145
|
+
*/
|
|
146
|
+
StateManager.prototype.update = function (updates) {
|
|
147
|
+
return __awaiter(this, void 0, void 0, function () {
|
|
148
|
+
var current, updated;
|
|
149
|
+
return __generator(this, function (_a) {
|
|
150
|
+
switch (_a.label) {
|
|
151
|
+
case 0: return [4 /*yield*/, this.load()];
|
|
152
|
+
case 1:
|
|
153
|
+
current = _a.sent();
|
|
154
|
+
if (!current) {
|
|
155
|
+
throw new Error("No existing state to update");
|
|
156
|
+
}
|
|
157
|
+
updated = __assign(__assign({}, current), updates);
|
|
158
|
+
return [4 /*yield*/, this.save(updated)];
|
|
159
|
+
case 2:
|
|
160
|
+
_a.sent();
|
|
161
|
+
return [2 /*return*/, updated];
|
|
162
|
+
}
|
|
163
|
+
});
|
|
164
|
+
});
|
|
165
|
+
};
|
|
166
|
+
return StateManager;
|
|
167
|
+
}());
|
|
168
|
+
exports.StateManager = StateManager;
|
package/src/tools.js
ADDED
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* GLM Daemon - Tool Bridge
|
|
4
|
+
*
|
|
5
|
+
* Connects GLM agents to MCP servers for tool access.
|
|
6
|
+
*/
|
|
7
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
8
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
9
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
10
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
11
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
12
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
13
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
14
|
+
});
|
|
15
|
+
};
|
|
16
|
+
var __generator = (this && this.__generator) || function (thisArg, body) {
|
|
17
|
+
var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g = Object.create((typeof Iterator === "function" ? Iterator : Object).prototype);
|
|
18
|
+
return g.next = verb(0), g["throw"] = verb(1), g["return"] = verb(2), typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g;
|
|
19
|
+
function verb(n) { return function (v) { return step([n, v]); }; }
|
|
20
|
+
function step(op) {
|
|
21
|
+
if (f) throw new TypeError("Generator is already executing.");
|
|
22
|
+
while (g && (g = 0, op[0] && (_ = 0)), _) try {
|
|
23
|
+
if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t;
|
|
24
|
+
if (y = 0, t) op = [op[0] & 2, t.value];
|
|
25
|
+
switch (op[0]) {
|
|
26
|
+
case 0: case 1: t = op; break;
|
|
27
|
+
case 4: _.label++; return { value: op[1], done: false };
|
|
28
|
+
case 5: _.label++; y = op[1]; op = [0]; continue;
|
|
29
|
+
case 7: op = _.ops.pop(); _.trys.pop(); continue;
|
|
30
|
+
default:
|
|
31
|
+
if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; }
|
|
32
|
+
if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; }
|
|
33
|
+
if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; }
|
|
34
|
+
if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; }
|
|
35
|
+
if (t[2]) _.ops.pop();
|
|
36
|
+
_.trys.pop(); continue;
|
|
37
|
+
}
|
|
38
|
+
op = body.call(thisArg, _);
|
|
39
|
+
} catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; }
|
|
40
|
+
if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true };
|
|
41
|
+
}
|
|
42
|
+
};
|
|
43
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
44
|
+
exports.ToolBridge = void 0;
|
|
45
|
+
var ToolBridge = /** @class */ (function () {
|
|
46
|
+
function ToolBridge(config) {
|
|
47
|
+
if (config === void 0) { config = {}; }
|
|
48
|
+
this.availableTools = [];
|
|
49
|
+
this.config = config;
|
|
50
|
+
this.toolTimeout = config.toolTimeout || 30000;
|
|
51
|
+
this.maxRetries = config.maxRetries || 3;
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Get list of available tools
|
|
55
|
+
*/
|
|
56
|
+
ToolBridge.prototype.getAvailableTools = function () {
|
|
57
|
+
return this.availableTools;
|
|
58
|
+
};
|
|
59
|
+
/**
|
|
60
|
+
* Set available tools (from MCP discovery)
|
|
61
|
+
*/
|
|
62
|
+
ToolBridge.prototype.setAvailableTools = function (tools) {
|
|
63
|
+
this.availableTools = tools;
|
|
64
|
+
};
|
|
65
|
+
/**
|
|
66
|
+
* Check if a tool is allowed
|
|
67
|
+
*/
|
|
68
|
+
ToolBridge.prototype.isToolAllowed = function (toolName) {
|
|
69
|
+
var _a;
|
|
70
|
+
// Check blacklist first
|
|
71
|
+
if ((_a = this.config.blockedTools) === null || _a === void 0 ? void 0 : _a.includes(toolName)) {
|
|
72
|
+
return false;
|
|
73
|
+
}
|
|
74
|
+
// If whitelist exists, check it
|
|
75
|
+
if (this.config.allowedTools && this.config.allowedTools.length > 0) {
|
|
76
|
+
return this.config.allowedTools.includes(toolName);
|
|
77
|
+
}
|
|
78
|
+
// Default: allow if not blocked
|
|
79
|
+
return true;
|
|
80
|
+
};
|
|
81
|
+
/**
|
|
82
|
+
* Execute a tool with timeout and retry
|
|
83
|
+
*/
|
|
84
|
+
ToolBridge.prototype.executeTool = function (toolName, args) {
|
|
85
|
+
return __awaiter(this, void 0, void 0, function () {
|
|
86
|
+
var lastError, _loop_1, this_1, attempt, state_1;
|
|
87
|
+
return __generator(this, function (_a) {
|
|
88
|
+
switch (_a.label) {
|
|
89
|
+
case 0:
|
|
90
|
+
if (!this.isToolAllowed(toolName)) {
|
|
91
|
+
return [2 /*return*/, {
|
|
92
|
+
success: false,
|
|
93
|
+
error: "Tool not allowed: ".concat(toolName),
|
|
94
|
+
}];
|
|
95
|
+
}
|
|
96
|
+
lastError = null;
|
|
97
|
+
_loop_1 = function (attempt) {
|
|
98
|
+
var result, error_1;
|
|
99
|
+
return __generator(this, function (_b) {
|
|
100
|
+
switch (_b.label) {
|
|
101
|
+
case 0:
|
|
102
|
+
_b.trys.push([0, 2, , 5]);
|
|
103
|
+
return [4 /*yield*/, this_1.executeWithTimeout(toolName, args)];
|
|
104
|
+
case 1:
|
|
105
|
+
result = _b.sent();
|
|
106
|
+
return [2 /*return*/, { value: { success: true, result: result } }];
|
|
107
|
+
case 2:
|
|
108
|
+
error_1 = _b.sent();
|
|
109
|
+
lastError = error_1;
|
|
110
|
+
if (!(attempt < this_1.maxRetries - 1)) return [3 /*break*/, 4];
|
|
111
|
+
return [4 /*yield*/, new Promise(function (resolve) {
|
|
112
|
+
return setTimeout(resolve, Math.pow(2, attempt) * 1000);
|
|
113
|
+
})];
|
|
114
|
+
case 3:
|
|
115
|
+
_b.sent();
|
|
116
|
+
_b.label = 4;
|
|
117
|
+
case 4: return [3 /*break*/, 5];
|
|
118
|
+
case 5: return [2 /*return*/];
|
|
119
|
+
}
|
|
120
|
+
});
|
|
121
|
+
};
|
|
122
|
+
this_1 = this;
|
|
123
|
+
attempt = 0;
|
|
124
|
+
_a.label = 1;
|
|
125
|
+
case 1:
|
|
126
|
+
if (!(attempt < this.maxRetries)) return [3 /*break*/, 4];
|
|
127
|
+
return [5 /*yield**/, _loop_1(attempt)];
|
|
128
|
+
case 2:
|
|
129
|
+
state_1 = _a.sent();
|
|
130
|
+
if (typeof state_1 === "object")
|
|
131
|
+
return [2 /*return*/, state_1.value];
|
|
132
|
+
_a.label = 3;
|
|
133
|
+
case 3:
|
|
134
|
+
attempt++;
|
|
135
|
+
return [3 /*break*/, 1];
|
|
136
|
+
case 4: return [2 /*return*/, {
|
|
137
|
+
success: false,
|
|
138
|
+
error: (lastError === null || lastError === void 0 ? void 0 : lastError.message) || "Unknown error",
|
|
139
|
+
}];
|
|
140
|
+
}
|
|
141
|
+
});
|
|
142
|
+
});
|
|
143
|
+
};
|
|
144
|
+
/**
|
|
145
|
+
* Execute tool with timeout
|
|
146
|
+
*/
|
|
147
|
+
ToolBridge.prototype.executeWithTimeout = function (toolName, args) {
|
|
148
|
+
return __awaiter(this, void 0, void 0, function () {
|
|
149
|
+
var _this = this;
|
|
150
|
+
return __generator(this, function (_a) {
|
|
151
|
+
return [2 /*return*/, new Promise(function (resolve, reject) {
|
|
152
|
+
var timer = setTimeout(function () {
|
|
153
|
+
reject(new Error("Tool timeout: ".concat(toolName)));
|
|
154
|
+
}, _this.toolTimeout);
|
|
155
|
+
// TODO: Actually call MCP tool here
|
|
156
|
+
// For now, this is a placeholder
|
|
157
|
+
setTimeout(function () {
|
|
158
|
+
clearTimeout(timer);
|
|
159
|
+
resolve("Executed ".concat(toolName, " with ").concat(JSON.stringify(args)));
|
|
160
|
+
}, 100);
|
|
161
|
+
})];
|
|
162
|
+
});
|
|
163
|
+
});
|
|
164
|
+
};
|
|
165
|
+
/**
|
|
166
|
+
* Discover tools from MCP servers
|
|
167
|
+
*/
|
|
168
|
+
ToolBridge.prototype.discoverMCPTools = function () {
|
|
169
|
+
return __awaiter(this, void 0, void 0, function () {
|
|
170
|
+
var commonTools;
|
|
171
|
+
return __generator(this, function (_a) {
|
|
172
|
+
if (!this.config.enableMCP) {
|
|
173
|
+
return [2 /*return*/];
|
|
174
|
+
}
|
|
175
|
+
commonTools = [
|
|
176
|
+
"Read",
|
|
177
|
+
"Write",
|
|
178
|
+
"Edit",
|
|
179
|
+
"Grep",
|
|
180
|
+
"Glob",
|
|
181
|
+
"Bash",
|
|
182
|
+
"git_status",
|
|
183
|
+
"git_commit",
|
|
184
|
+
];
|
|
185
|
+
this.setAvailableTools(commonTools);
|
|
186
|
+
return [2 /*return*/];
|
|
187
|
+
});
|
|
188
|
+
});
|
|
189
|
+
};
|
|
190
|
+
return ToolBridge;
|
|
191
|
+
}());
|
|
192
|
+
exports.ToolBridge = ToolBridge;
|