letagents 0.8.1 → 0.10.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/dist/mcp/local-state.js +94 -11
- package/dist/mcp/room-id.js +3 -0
- package/dist/mcp/server.js +814 -79
- package/dist/shared/agent-identity.js +104 -0
- package/package.json +2 -1
package/dist/mcp/local-state.js
CHANGED
|
@@ -1,12 +1,16 @@
|
|
|
1
|
-
import { existsSync, mkdirSync, readFileSync, renameSync, writeFileSync } from "fs";
|
|
1
|
+
import { closeSync, existsSync, mkdirSync, openSync, readFileSync, renameSync, rmSync, statSync, writeFileSync, } from "fs";
|
|
2
|
+
import { randomBytes } from "crypto";
|
|
2
3
|
import { homedir } from "os";
|
|
3
4
|
import { dirname, join } from "path";
|
|
4
5
|
const DEFAULT_STATE_PATH = join(homedir(), ".letagents", "mcp-state.json");
|
|
6
|
+
const STATE_LOCK_WAIT_MS = 25;
|
|
7
|
+
const STATE_LOCK_TIMEOUT_MS = 2_000;
|
|
8
|
+
const STATE_LOCK_STALE_MS = 10_000;
|
|
9
|
+
const STATE_LOCK_SLEEP_BUFFER = new Int32Array(new SharedArrayBuffer(4));
|
|
5
10
|
export function getLocalStatePath() {
|
|
6
11
|
return process.env.LETAGENTS_STATE_PATH || DEFAULT_STATE_PATH;
|
|
7
12
|
}
|
|
8
|
-
|
|
9
|
-
const statePath = getLocalStatePath();
|
|
13
|
+
function readLocalStateFromPath(statePath) {
|
|
10
14
|
if (!existsSync(statePath)) {
|
|
11
15
|
return {};
|
|
12
16
|
}
|
|
@@ -19,18 +23,76 @@ export function readLocalState() {
|
|
|
19
23
|
return {};
|
|
20
24
|
}
|
|
21
25
|
}
|
|
22
|
-
export function
|
|
26
|
+
export function readLocalState() {
|
|
27
|
+
const statePath = getLocalStatePath();
|
|
28
|
+
return readLocalStateFromPath(statePath);
|
|
29
|
+
}
|
|
30
|
+
function sleepSync(ms) {
|
|
31
|
+
if (ms > 0) {
|
|
32
|
+
Atomics.wait(STATE_LOCK_SLEEP_BUFFER, 0, 0, ms);
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
function writeLocalStateUnlocked(statePath, state) {
|
|
36
|
+
const tempPath = `${statePath}.${process.pid}.${randomBytes(6).toString("hex")}.tmp`;
|
|
37
|
+
try {
|
|
38
|
+
writeFileSync(tempPath, JSON.stringify(state, null, 2) + "\n", "utf-8");
|
|
39
|
+
renameSync(tempPath, statePath);
|
|
40
|
+
}
|
|
41
|
+
finally {
|
|
42
|
+
rmSync(tempPath, { force: true });
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
function withStateLock(callback) {
|
|
23
46
|
const statePath = getLocalStatePath();
|
|
24
47
|
mkdirSync(dirname(statePath), { recursive: true });
|
|
25
|
-
const
|
|
26
|
-
|
|
27
|
-
|
|
48
|
+
const lockPath = `${statePath}.lock`;
|
|
49
|
+
const startedAt = Date.now();
|
|
50
|
+
while (true) {
|
|
51
|
+
let lockFd = null;
|
|
52
|
+
try {
|
|
53
|
+
lockFd = openSync(lockPath, "wx");
|
|
54
|
+
return callback(statePath);
|
|
55
|
+
}
|
|
56
|
+
catch (error) {
|
|
57
|
+
const err = error;
|
|
58
|
+
if (err.code !== "EEXIST") {
|
|
59
|
+
throw error;
|
|
60
|
+
}
|
|
61
|
+
try {
|
|
62
|
+
const stats = statSync(lockPath);
|
|
63
|
+
if (Date.now() - stats.mtimeMs > STATE_LOCK_STALE_MS) {
|
|
64
|
+
rmSync(lockPath, { force: true });
|
|
65
|
+
continue;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
catch {
|
|
69
|
+
continue;
|
|
70
|
+
}
|
|
71
|
+
if (Date.now() - startedAt >= STATE_LOCK_TIMEOUT_MS) {
|
|
72
|
+
throw new Error(`Timed out acquiring local state lock at ${lockPath}`);
|
|
73
|
+
}
|
|
74
|
+
sleepSync(STATE_LOCK_WAIT_MS);
|
|
75
|
+
}
|
|
76
|
+
finally {
|
|
77
|
+
if (lockFd !== null) {
|
|
78
|
+
closeSync(lockFd);
|
|
79
|
+
rmSync(lockPath, { force: true });
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
export function writeLocalState(state) {
|
|
85
|
+
withStateLock((statePath) => {
|
|
86
|
+
writeLocalStateUnlocked(statePath, state);
|
|
87
|
+
});
|
|
28
88
|
}
|
|
29
89
|
export function updateLocalState(updater) {
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
90
|
+
return withStateLock((statePath) => {
|
|
91
|
+
const current = readLocalStateFromPath(statePath);
|
|
92
|
+
const updated = updater(current) ?? current;
|
|
93
|
+
writeLocalStateUnlocked(statePath, updated);
|
|
94
|
+
return updated;
|
|
95
|
+
});
|
|
34
96
|
}
|
|
35
97
|
function isExpired(expiresAt) {
|
|
36
98
|
if (!expiresAt) {
|
|
@@ -88,6 +150,27 @@ export function clearPendingDeviceAuth() {
|
|
|
88
150
|
return state;
|
|
89
151
|
});
|
|
90
152
|
}
|
|
153
|
+
export function getStoredAgentIdentity(identityKey) {
|
|
154
|
+
const state = readLocalState();
|
|
155
|
+
if (identityKey?.trim()) {
|
|
156
|
+
const scoped = state.agent_identities?.[identityKey.trim()];
|
|
157
|
+
if (scoped) {
|
|
158
|
+
return scoped;
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
return state.agent_identity ?? null;
|
|
162
|
+
}
|
|
163
|
+
export function setStoredAgentIdentity(agentIdentity, identityKey) {
|
|
164
|
+
updateLocalState((state) => {
|
|
165
|
+
state.agent_identity = agentIdentity;
|
|
166
|
+
if (identityKey?.trim()) {
|
|
167
|
+
state.agent_identities = state.agent_identities ?? {};
|
|
168
|
+
state.agent_identities[identityKey.trim()] = agentIdentity;
|
|
169
|
+
}
|
|
170
|
+
return state;
|
|
171
|
+
});
|
|
172
|
+
return agentIdentity;
|
|
173
|
+
}
|
|
91
174
|
export function getStoredCurrentRoom() {
|
|
92
175
|
const state = readLocalState();
|
|
93
176
|
return state.current_room ?? null;
|
package/dist/mcp/room-id.js
CHANGED
|
@@ -4,6 +4,9 @@ export function encodeRoomIdPath(roomId) {
|
|
|
4
4
|
.map((segment) => encodeURIComponent(segment))
|
|
5
5
|
.join("/");
|
|
6
6
|
}
|
|
7
|
+
export function getCanonicalRoomWebPath(roomId) {
|
|
8
|
+
return `/in/${encodeRoomIdPath(roomId)}`;
|
|
9
|
+
}
|
|
7
10
|
export function looksLikeInviteCode(value) {
|
|
8
11
|
return /^[A-Z0-9]{4}(?:-[A-Z0-9]{4})+$/.test(value.trim().toUpperCase());
|
|
9
12
|
}
|