@melaya/runner 1.0.13 → 1.0.15
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/localRelay.js +25 -7
- package/dist/sharedVendor.js +48 -9
- package/package.json +1 -1
package/dist/localRelay.js
CHANGED
|
@@ -20,20 +20,38 @@ let _throttleQueue = [];
|
|
|
20
20
|
function _flushThrottle(socket, verbose) {
|
|
21
21
|
if (_throttleQueue.length === 0)
|
|
22
22
|
return;
|
|
23
|
-
//
|
|
24
|
-
|
|
23
|
+
// Coalescing rule:
|
|
24
|
+
// - `agent_message` is a streaming event type whose payload GROWS
|
|
25
|
+
// over time (each update replaces the previous with more text).
|
|
26
|
+
// Only the LAST per replyId matters — keep the last-write-wins
|
|
27
|
+
// dedup for these.
|
|
28
|
+
// - Everything else (reasoning / tool_call / tool_result /
|
|
29
|
+
// agent_reply / cost / session_status / compact / hook) is a
|
|
30
|
+
// discrete event — coalescing drops real information. Emit every
|
|
31
|
+
// one exactly once, preserving order.
|
|
32
|
+
const streamingByReply = new Map();
|
|
33
|
+
const discrete = [];
|
|
25
34
|
for (const p of _throttleQueue) {
|
|
26
|
-
|
|
27
|
-
|
|
35
|
+
if (p.event_type === "agent_message") {
|
|
36
|
+
const key = p.replyId || p.run_id || "default";
|
|
37
|
+
streamingByReply.set(key, p);
|
|
38
|
+
}
|
|
39
|
+
else {
|
|
40
|
+
discrete.push(p);
|
|
41
|
+
}
|
|
28
42
|
}
|
|
29
|
-
|
|
43
|
+
const emit = (payload) => {
|
|
30
44
|
socket.emit("runner:event", payload);
|
|
31
45
|
if (verbose) {
|
|
32
46
|
const type = payload.event_type || "unknown";
|
|
33
|
-
const agent = payload.replyName || "";
|
|
47
|
+
const agent = payload.replyName || payload.agent_name || "";
|
|
34
48
|
process.stdout.write(` [relay] ${type}${agent ? ` (${agent})` : ""}\n`);
|
|
35
49
|
}
|
|
36
|
-
}
|
|
50
|
+
};
|
|
51
|
+
for (const payload of discrete)
|
|
52
|
+
emit(payload);
|
|
53
|
+
for (const payload of streamingByReply.values())
|
|
54
|
+
emit(payload);
|
|
37
55
|
_throttleQueue = [];
|
|
38
56
|
_throttleTimer = null;
|
|
39
57
|
}
|
package/dist/sharedVendor.js
CHANGED
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
* runtime from the server's /api/v1/agents/shared-bundle endpoint,
|
|
8
8
|
* integrity-verified via SHA-256, and cached at ~/.melaya-runner/shared/.
|
|
9
9
|
*/
|
|
10
|
-
import { mkdirSync, writeFileSync, readFileSync, existsSync } from "fs";
|
|
10
|
+
import { mkdirSync, writeFileSync, readFileSync, existsSync, rmSync } from "fs";
|
|
11
11
|
import { join } from "path";
|
|
12
12
|
import { homedir } from "os";
|
|
13
13
|
import { createHash } from "crypto";
|
|
@@ -21,14 +21,38 @@ export function getSharedDir() {
|
|
|
21
21
|
// and `from crews.X.main import ...` all resolve.
|
|
22
22
|
return CACHE_DIR;
|
|
23
23
|
}
|
|
24
|
+
/**
|
|
25
|
+
* Canary files that prove the current nested bundle layout has been written
|
|
26
|
+
* to disk. A cache where any of these is missing was produced by an older
|
|
27
|
+
* (flat-layout) bundler and must be refetched regardless of version match.
|
|
28
|
+
*/
|
|
29
|
+
const CANARY_FILES = [
|
|
30
|
+
join(CACHE_DIR, "shared", "runtime", "agent_factory.py"),
|
|
31
|
+
join(CACHE_DIR, "shared", "runtime", "__init__.py"),
|
|
32
|
+
join(CACHE_DIR, "shared", "tools", "__init__.py"),
|
|
33
|
+
join(CACHE_DIR, "agentscope", "__init__.py"),
|
|
34
|
+
];
|
|
35
|
+
function cacheLooksValid() {
|
|
36
|
+
return CANARY_FILES.every((p) => existsSync(p));
|
|
37
|
+
}
|
|
24
38
|
export async function ensureSharedModules(serverUrl, expectedVersion) {
|
|
25
39
|
mkdirSync(CACHE_DIR, { recursive: true });
|
|
26
|
-
// Check cached version
|
|
40
|
+
// Check cached version. We require BOTH a version match AND a valid nested
|
|
41
|
+
// layout — older runner versions wrote flat filenames into shared/, and
|
|
42
|
+
// those caches must be purged rather than trusted.
|
|
27
43
|
if (existsSync(VERSION_FILE)) {
|
|
28
44
|
const cached = readFileSync(VERSION_FILE, "utf-8").trim();
|
|
29
|
-
if (cached === expectedVersion &&
|
|
30
|
-
return;
|
|
45
|
+
if (cached === expectedVersion && cacheLooksValid()) {
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
// Purge stale cache — flat-layout shared/ or missing agentscope dir.
|
|
50
|
+
// This is safe: we always refetch the full bundle below.
|
|
51
|
+
if (existsSync(SHARED_DIR) && !cacheLooksValid()) {
|
|
52
|
+
try {
|
|
53
|
+
rmSync(SHARED_DIR, { recursive: true, force: true });
|
|
31
54
|
}
|
|
55
|
+
catch { /* best-effort */ }
|
|
32
56
|
}
|
|
33
57
|
// Fetch from server
|
|
34
58
|
const httpUrl = serverUrl.replace(/^wss:/, "https:").replace(/^ws:/, "http:");
|
|
@@ -44,6 +68,7 @@ export async function ensureSharedModules(serverUrl, expectedVersion) {
|
|
|
44
68
|
throw new Error("Shared module integrity check failed — hash mismatch");
|
|
45
69
|
}
|
|
46
70
|
// Write files (sanitize filenames to prevent path traversal)
|
|
71
|
+
const writtenDirs = new Set();
|
|
47
72
|
for (const [filename, content] of Object.entries(body.files)) {
|
|
48
73
|
const safe = filename.replace(/\.\./g, "").replace(/^\//, "").replace(/\\/g, "/");
|
|
49
74
|
if (safe.includes("..") || safe.startsWith("/"))
|
|
@@ -54,13 +79,27 @@ export async function ensureSharedModules(serverUrl, expectedVersion) {
|
|
|
54
79
|
const dir = join(filepath, "..");
|
|
55
80
|
mkdirSync(dir, { recursive: true });
|
|
56
81
|
writeFileSync(filepath, content, "utf-8");
|
|
82
|
+
// Track every ancestor directory under CACHE_DIR so we can ensure each
|
|
83
|
+
// has an __init__.py for Python package resolution.
|
|
84
|
+
let cursor = dir;
|
|
85
|
+
while (cursor.startsWith(CACHE_DIR) && cursor !== CACHE_DIR) {
|
|
86
|
+
writtenDirs.add(cursor);
|
|
87
|
+
cursor = join(cursor, "..");
|
|
88
|
+
}
|
|
57
89
|
}
|
|
58
|
-
// Ensure __init__.py in shared/ and crews
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
90
|
+
// Ensure __init__.py in every nested directory of shared/ and crews/.
|
|
91
|
+
// agentscope ships its own __init__.py files as part of the bundle. This
|
|
92
|
+
// covers cases where the server didn't include an __init__.py for a
|
|
93
|
+
// particular subdir (e.g. accidentally removed) — Python won't treat it
|
|
94
|
+
// as a package without one, breaking imports like `shared.runtime.*`.
|
|
95
|
+
for (const dir of writtenDirs) {
|
|
96
|
+
const rel = dir.slice(CACHE_DIR.length + 1).replace(/\\/g, "/");
|
|
97
|
+
const topLevel = rel.split("/")[0];
|
|
98
|
+
if (topLevel !== "shared" && topLevel !== "crews")
|
|
99
|
+
continue;
|
|
100
|
+
const init = join(dir, "__init__.py");
|
|
101
|
+
if (!existsSync(init))
|
|
62
102
|
writeFileSync(init, "", "utf-8");
|
|
63
|
-
}
|
|
64
103
|
}
|
|
65
104
|
writeFileSync(VERSION_FILE, expectedVersion, "utf-8");
|
|
66
105
|
}
|