@nanhara/hara 0.123.1 → 0.124.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/CHANGELOG.md +49 -0
- package/README.md +12 -9
- package/dist/agent/compact.js +98 -18
- package/dist/agent/context-budget.js +180 -0
- package/dist/agent/evolution.js +37 -0
- package/dist/agent/failover.js +3 -4
- package/dist/agent/loop.js +39 -4
- package/dist/config.js +27 -90
- package/dist/cron/store.js +4 -3
- package/dist/desk.js +8 -14
- package/dist/fs-identity.js +11 -0
- package/dist/fs-open-flags.js +14 -0
- package/dist/fs-read.js +38 -22
- package/dist/fs-write.js +4 -3
- package/dist/gateway/outbound-files.js +7 -8
- package/dist/gateway/runtime-state.js +11 -20
- package/dist/gateway/weixin.js +44 -42
- package/dist/index.js +240 -54
- package/dist/org/review-chain.js +2 -2
- package/dist/org-fleet/enroll.js +17 -22
- package/dist/plugins/plugins.js +7 -9
- package/dist/profile/profile.js +29 -56
- package/dist/providers/qwen-oauth.js +6 -19
- package/dist/security/permissions.js +4 -3
- package/dist/security/private-state.js +324 -11
- package/dist/serve/server.js +65 -39
- package/dist/serve/sessions.js +12 -4
- package/dist/session/store.js +4 -0
- package/dist/session/task.js +70 -8
- package/dist/tools/memory.js +18 -3
- package/dist/tools/search.js +6 -2
- package/dist/tui/App.js +43 -17
- package/package.json +1 -1
package/dist/gateway/weixin.js
CHANGED
|
@@ -4,11 +4,10 @@
|
|
|
4
4
|
// `qrcode-terminal` dep (graceful fallback to printing the URL). No encryption is needed on the text path
|
|
5
5
|
// (iLink only uses crypto for media upload/download, which v1 doesn't do).
|
|
6
6
|
import { homedir } from "node:os";
|
|
7
|
-
import { join } from "node:path";
|
|
8
|
-
import { chmodSync, readFileSync, writeFileSync, existsSync, mkdirSync, renameSync, rmSync } from "node:fs";
|
|
9
7
|
import { randomBytes, randomUUID, createHash, createCipheriv, createDecipheriv } from "node:crypto";
|
|
10
8
|
import { chunkText } from "./telegram.js";
|
|
11
9
|
import { INBOUND_MEDIA_MAX_BYTES, INBOUND_MEDIA_TIMEOUT_MS, InboundMediaBudget, cleanupTransientMedia, readResponseBytesLimited, savePrivateMediaBytes, } from "./media.js";
|
|
10
|
+
import { bindPrivateHaraStateFile, readPrivateStateFileSnapshotSync, writePrivateStateFileSync, } from "../security/private-state.js";
|
|
12
11
|
const ILINK_BASE_URL = "https://ilinkai.weixin.qq.com";
|
|
13
12
|
const CHANNEL_VERSION = "2.2.0";
|
|
14
13
|
const ILINK_APP_ID = "bot";
|
|
@@ -186,33 +185,53 @@ async function apiGet(baseUrl, endpoint, timeoutMs) {
|
|
|
186
185
|
throw new Error(`iLink GET ${endpoint} HTTP ${res.status}: ${raw.slice(0, 200)}`);
|
|
187
186
|
return JSON.parse(raw);
|
|
188
187
|
}
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
188
|
+
function weixinStateFile(filename) {
|
|
189
|
+
return bindPrivateHaraStateFile(homedir(), ["weixin"], filename);
|
|
190
|
+
}
|
|
191
|
+
function accountStateFilename(accountId, suffix) {
|
|
192
|
+
const normalized = String(accountId ?? "").trim();
|
|
193
|
+
const key = /^[A-Za-z0-9_-]{1,128}$/.test(normalized)
|
|
194
|
+
? normalized
|
|
195
|
+
: `account-${createHash("sha256").update(normalized).digest("hex")}`;
|
|
196
|
+
return `${key}${suffix}`;
|
|
197
|
+
}
|
|
198
|
+
/** Return peer identifiers without exposing token values or bypassing the private-state reader. */
|
|
199
|
+
export function weixinKnownPeers(accountId) {
|
|
192
200
|
try {
|
|
193
|
-
|
|
201
|
+
const binding = weixinStateFile(accountStateFilename(accountId, ".context-tokens.json"));
|
|
202
|
+
const snapshot = readPrivateStateFileSnapshotSync(binding.path, 8 * 1024 * 1024);
|
|
203
|
+
if (!snapshot)
|
|
204
|
+
return [];
|
|
205
|
+
const parsed = JSON.parse(snapshot.text);
|
|
206
|
+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed))
|
|
207
|
+
return [];
|
|
208
|
+
return Object.entries(parsed)
|
|
209
|
+
.slice(-1000)
|
|
210
|
+
.filter(([peer, token]) => Boolean(peer) && typeof token === "string" && Boolean(token))
|
|
211
|
+
.map(([peer]) => peer);
|
|
194
212
|
}
|
|
195
213
|
catch {
|
|
196
|
-
return
|
|
214
|
+
return [];
|
|
197
215
|
}
|
|
198
216
|
}
|
|
199
|
-
function
|
|
200
|
-
mkdirSync(weixinDir(), { recursive: true, mode: 0o700 });
|
|
217
|
+
export function loadWeixinCreds() {
|
|
201
218
|
try {
|
|
202
|
-
|
|
219
|
+
const binding = weixinStateFile("creds.json");
|
|
220
|
+
const snapshot = readPrivateStateFileSnapshotSync(binding.path, 1024 * 1024);
|
|
221
|
+
return snapshot ? JSON.parse(snapshot.text) : null;
|
|
203
222
|
}
|
|
204
|
-
catch {
|
|
205
|
-
|
|
206
|
-
try {
|
|
207
|
-
chmodSync(credsFile(), 0o600);
|
|
223
|
+
catch {
|
|
224
|
+
return null;
|
|
208
225
|
}
|
|
209
|
-
|
|
226
|
+
}
|
|
227
|
+
function saveWeixinCreds(c) {
|
|
228
|
+
writePrivateStateFileSync(weixinStateFile("creds.json"), JSON.stringify(c, null, 2) + "\n");
|
|
210
229
|
}
|
|
211
230
|
// get_updates_buf cursor — persisted so a restart resumes the message stream where it left off.
|
|
212
231
|
function loadCursor(accountId) {
|
|
213
232
|
try {
|
|
214
|
-
const
|
|
215
|
-
return
|
|
233
|
+
const binding = weixinStateFile(accountStateFilename(accountId, ".cursor"));
|
|
234
|
+
return readPrivateStateFileSnapshotSync(binding.path, 4 * 1024 * 1024)?.text ?? "";
|
|
216
235
|
}
|
|
217
236
|
catch {
|
|
218
237
|
return "";
|
|
@@ -220,11 +239,8 @@ function loadCursor(accountId) {
|
|
|
220
239
|
}
|
|
221
240
|
function saveCursor(accountId, buf) {
|
|
222
241
|
try {
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
const file = join(weixinDir(), `${accountId}.cursor`);
|
|
226
|
-
writeFileSync(file, buf, { mode: 0o600 });
|
|
227
|
-
chmodSync(file, 0o600);
|
|
242
|
+
const binding = weixinStateFile(accountStateFilename(accountId, ".cursor"));
|
|
243
|
+
writePrivateStateFileSync(binding, buf);
|
|
228
244
|
}
|
|
229
245
|
catch {
|
|
230
246
|
/* best-effort */
|
|
@@ -237,10 +253,9 @@ class TokenStore {
|
|
|
237
253
|
constructor(accountId) {
|
|
238
254
|
this.accountId = accountId;
|
|
239
255
|
try {
|
|
240
|
-
const
|
|
241
|
-
const
|
|
242
|
-
|
|
243
|
-
chmodSync(this.file(), 0o600);
|
|
256
|
+
const binding = this.binding();
|
|
257
|
+
const snapshot = readPrivateStateFileSnapshotSync(binding.path, 8 * 1024 * 1024);
|
|
258
|
+
const parsed = snapshot ? JSON.parse(snapshot.text) : {};
|
|
244
259
|
if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
|
|
245
260
|
for (const [peer, token] of Object.entries(parsed).slice(-1000)) {
|
|
246
261
|
if (peer && typeof token === "string" && token)
|
|
@@ -252,32 +267,19 @@ class TokenStore {
|
|
|
252
267
|
this.cache.clear();
|
|
253
268
|
}
|
|
254
269
|
}
|
|
255
|
-
|
|
256
|
-
return
|
|
270
|
+
binding() {
|
|
271
|
+
return weixinStateFile(accountStateFilename(this.accountId, ".context-tokens.json"));
|
|
257
272
|
}
|
|
258
273
|
peerKey(peer) {
|
|
259
274
|
return String(peer ?? "").trim().slice(0, 256);
|
|
260
275
|
}
|
|
261
276
|
persist() {
|
|
262
|
-
const temporary = `${this.file()}.${process.pid}.${randomUUID()}.tmp`;
|
|
263
277
|
try {
|
|
264
|
-
|
|
265
|
-
chmodSync(weixinDir(), 0o700);
|
|
266
|
-
writeFileSync(temporary, JSON.stringify(Object.fromEntries(this.cache)), { mode: 0o600, flag: "wx" });
|
|
267
|
-
renameSync(temporary, this.file());
|
|
268
|
-
chmodSync(this.file(), 0o600);
|
|
278
|
+
writePrivateStateFileSync(this.binding(), JSON.stringify(Object.fromEntries(this.cache)) + "\n");
|
|
269
279
|
}
|
|
270
280
|
catch {
|
|
271
281
|
/* best-effort */
|
|
272
282
|
}
|
|
273
|
-
finally {
|
|
274
|
-
try {
|
|
275
|
-
rmSync(temporary, { force: true });
|
|
276
|
-
}
|
|
277
|
-
catch {
|
|
278
|
-
/* best-effort */
|
|
279
|
-
}
|
|
280
|
-
}
|
|
281
283
|
}
|
|
282
284
|
get(peer) {
|
|
283
285
|
const key = this.peerKey(peer);
|