neoctl-web 0.1.0 → 0.1.2
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 +244 -238
- package/bin/neow.mjs +139 -123
- package/core-runtime.mjs +55 -55
- package/cpa-quota.mjs +209 -209
- package/dist/assets/{index-H7num-0s.js → index-B6NPbDJ9.js} +1 -1
- package/dist/assets/{index-BSFq6wfd.css → index-DG8NYKAP.css} +1 -1
- package/dist/favicon.svg +3 -3
- package/dist/index.html +2 -2
- package/memory-monitor.mjs +150 -150
- package/package.json +63 -62
- package/platform-paths.mjs +40 -0
- package/plugin-settings.mjs +67 -67
- package/plugins/downloads/downloads.mjs +147 -147
- package/plugins/downloads/index.mjs +19 -19
- package/plugins/downloads/neo-plugin.json +9 -9
- package/plugins/xhs-artifact/artifacts.mjs +388 -388
- package/plugins/xhs-artifact/editor-page.mjs +73 -73
- package/plugins/xhs-artifact/index.mjs +48 -48
- package/plugins/xhs-artifact/neo-plugin.json +9 -9
- package/plugins.mjs +111 -111
- package/runtime-router-cleanup.mjs +152 -152
- package/runtime-workspaces.mjs +515 -515
- package/server.mjs +447 -445
- package/tool-settings.mjs +80 -80
|
@@ -1,152 +1,152 @@
|
|
|
1
|
-
import { WebRuntimeRouter } from './core-runtime.mjs';
|
|
2
|
-
|
|
3
|
-
const INSTALL_KEY = Symbol.for('neoctl-web.runtime-router-session-hubs');
|
|
4
|
-
const routerTimers = new WeakMap();
|
|
5
|
-
const routerAccessTimes = new WeakMap();
|
|
6
|
-
|
|
7
|
-
export function installRuntimeRouterIdleCleanup(options = {}) {
|
|
8
|
-
const prototype = WebRuntimeRouter.prototype;
|
|
9
|
-
if (prototype[INSTALL_KEY]) return;
|
|
10
|
-
prototype[INSTALL_KEY] = true;
|
|
11
|
-
|
|
12
|
-
const idleMs = Math.max(60_000, positiveNumber(options.idleMs || process.env.NEO_RUNTIME_IDLE_MS, 15 * 60_000));
|
|
13
|
-
const maxSessions = Math.max(1, positiveNumber(options.maxSessions || process.env.NEO_RUNTIME_MAX_SESSIONS, 64));
|
|
14
|
-
const originalGet = prototype.get;
|
|
15
|
-
|
|
16
|
-
prototype.get = function getSharedSessionRuntime(scope = {}) {
|
|
17
|
-
const tabId = normalizeScopeValue(scope.tabId);
|
|
18
|
-
const sessionId = normalizeScopeValue(scope.sessionId);
|
|
19
|
-
const key = sessionId ? `session:${sessionId}` : tabId ? `tab:${tabId}` : '__default__';
|
|
20
|
-
let result = this.repls?.get(key);
|
|
21
|
-
|
|
22
|
-
if (!result && sessionId && tabId) {
|
|
23
|
-
const tabKey = `tab:${tabId}`;
|
|
24
|
-
const bootstrap = this.repls?.get(tabKey);
|
|
25
|
-
if (bootstrap) {
|
|
26
|
-
result = promoteBootstrapRuntime(this, originalGet, bootstrap, tabKey, key, sessionId);
|
|
27
|
-
this.repls.set(key, result);
|
|
28
|
-
}
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
if (!result) {
|
|
32
|
-
result = originalGet.call(this, sessionId ? { sessionId } : tabId ? { tabId } : {});
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
touchRuntime(this, key);
|
|
36
|
-
scheduleCleanup(this, key, result, idleMs);
|
|
37
|
-
void enforceSessionLimit(this, maxSessions);
|
|
38
|
-
return result;
|
|
39
|
-
};
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
function promoteBootstrapRuntime(router, originalGet, bootstrap, tabKey, sessionKey, sessionId) {
|
|
43
|
-
let promoted;
|
|
44
|
-
promoted = bootstrap.then(async (repl) => {
|
|
45
|
-
if (runtimeSessionId(repl) === sessionId) {
|
|
46
|
-
if (router.repls?.get(tabKey) === bootstrap) router.repls.delete(tabKey);
|
|
47
|
-
clearRuntimeTimer(router, tabKey);
|
|
48
|
-
clearRuntimeAccessTime(router, tabKey);
|
|
49
|
-
return repl;
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
if (router.repls?.get(sessionKey) === promoted) router.repls.delete(sessionKey);
|
|
53
|
-
return originalGet.call(router, { sessionId });
|
|
54
|
-
});
|
|
55
|
-
return promoted;
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
function scheduleCleanup(router, key, replPromise, idleMs) {
|
|
59
|
-
let timers = routerTimers.get(router);
|
|
60
|
-
if (!timers) {
|
|
61
|
-
timers = new Map();
|
|
62
|
-
routerTimers.set(router, timers);
|
|
63
|
-
}
|
|
64
|
-
clearTimeout(timers.get(key));
|
|
65
|
-
const timer = setTimeout(async () => {
|
|
66
|
-
timers.delete(key);
|
|
67
|
-
if (router.repls?.get(key) !== replPromise) return;
|
|
68
|
-
let repl;
|
|
69
|
-
try { repl = await replPromise; } catch { return; }
|
|
70
|
-
if (isRuntimeActive(repl)) {
|
|
71
|
-
scheduleCleanup(router, key, replPromise, idleMs);
|
|
72
|
-
return;
|
|
73
|
-
}
|
|
74
|
-
router.repls.delete(key);
|
|
75
|
-
clearRuntimeAccessTime(router, key);
|
|
76
|
-
}, idleMs);
|
|
77
|
-
timer.unref?.();
|
|
78
|
-
timers.set(key, timer);
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
async function enforceSessionLimit(router, maxSessions) {
|
|
82
|
-
const entries = [...(router.repls?.entries() || [])]
|
|
83
|
-
.filter(([key]) => key.startsWith('session:'));
|
|
84
|
-
if (entries.length <= maxSessions) return;
|
|
85
|
-
|
|
86
|
-
const accessTimes = routerAccessTimes.get(router) || new Map();
|
|
87
|
-
entries.sort(([left], [right]) => (accessTimes.get(left) || 0) - (accessTimes.get(right) || 0));
|
|
88
|
-
let overflow = entries.length - maxSessions;
|
|
89
|
-
for (const [key, promise] of entries) {
|
|
90
|
-
if (overflow <= 0) break;
|
|
91
|
-
if (router.repls?.get(key) !== promise) continue;
|
|
92
|
-
let repl;
|
|
93
|
-
try { repl = await promise; } catch { repl = undefined; }
|
|
94
|
-
if (repl && isRuntimeActive(repl)) continue;
|
|
95
|
-
if (router.repls?.get(key) !== promise) continue;
|
|
96
|
-
router.repls.delete(key);
|
|
97
|
-
clearRuntimeTimer(router, key);
|
|
98
|
-
clearRuntimeAccessTime(router, key);
|
|
99
|
-
overflow -= 1;
|
|
100
|
-
}
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
function isRuntimeActive(repl) {
|
|
104
|
-
return Boolean(
|
|
105
|
-
repl?.busy
|
|
106
|
-
|| repl?.subscribers?.size > 0
|
|
107
|
-
|| repl?.backgroundSessionRuns?.size > 0
|
|
108
|
-
|| Number(repl?.backgroundTaskCount || 0) > 0
|
|
109
|
-
);
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
function runtimeSessionId(repl) {
|
|
113
|
-
return String(repl?.runtime?.engine?.snapshot?.().session?.sessionId || '').trim();
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
function touchRuntime(router, key) {
|
|
117
|
-
let accessTimes = routerAccessTimes.get(router);
|
|
118
|
-
if (!accessTimes) {
|
|
119
|
-
accessTimes = new Map();
|
|
120
|
-
routerAccessTimes.set(router, accessTimes);
|
|
121
|
-
}
|
|
122
|
-
accessTimes.set(key, Date.now());
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
function clearRuntimeTimer(router, key) {
|
|
126
|
-
const timers = routerTimers.get(router);
|
|
127
|
-
if (!timers) return;
|
|
128
|
-
clearTimeout(timers.get(key));
|
|
129
|
-
timers.delete(key);
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
function clearRuntimeAccessTime(router, key) {
|
|
133
|
-
routerAccessTimes.get(router)?.delete(key);
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
function normalizeScopeValue(value) {
|
|
137
|
-
const normalized = String(value || '').trim();
|
|
138
|
-
return normalized || undefined;
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
function positiveNumber(value, fallback) {
|
|
142
|
-
const parsed = Number(value);
|
|
143
|
-
return Number.isFinite(parsed) && parsed > 0 ? parsed : fallback;
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
export function runtimeScopeKey(scope = {}) {
|
|
147
|
-
const sessionId = normalizeScopeValue(scope.sessionId);
|
|
148
|
-
if (sessionId) return `session:${sessionId}`;
|
|
149
|
-
const tabId = normalizeScopeValue(scope.tabId);
|
|
150
|
-
if (tabId) return `tab:${tabId}`;
|
|
151
|
-
return '__default__';
|
|
152
|
-
}
|
|
1
|
+
import { WebRuntimeRouter } from './core-runtime.mjs';
|
|
2
|
+
|
|
3
|
+
const INSTALL_KEY = Symbol.for('neoctl-web.runtime-router-session-hubs');
|
|
4
|
+
const routerTimers = new WeakMap();
|
|
5
|
+
const routerAccessTimes = new WeakMap();
|
|
6
|
+
|
|
7
|
+
export function installRuntimeRouterIdleCleanup(options = {}) {
|
|
8
|
+
const prototype = WebRuntimeRouter.prototype;
|
|
9
|
+
if (prototype[INSTALL_KEY]) return;
|
|
10
|
+
prototype[INSTALL_KEY] = true;
|
|
11
|
+
|
|
12
|
+
const idleMs = Math.max(60_000, positiveNumber(options.idleMs || process.env.NEO_RUNTIME_IDLE_MS, 15 * 60_000));
|
|
13
|
+
const maxSessions = Math.max(1, positiveNumber(options.maxSessions || process.env.NEO_RUNTIME_MAX_SESSIONS, 64));
|
|
14
|
+
const originalGet = prototype.get;
|
|
15
|
+
|
|
16
|
+
prototype.get = function getSharedSessionRuntime(scope = {}) {
|
|
17
|
+
const tabId = normalizeScopeValue(scope.tabId);
|
|
18
|
+
const sessionId = normalizeScopeValue(scope.sessionId);
|
|
19
|
+
const key = sessionId ? `session:${sessionId}` : tabId ? `tab:${tabId}` : '__default__';
|
|
20
|
+
let result = this.repls?.get(key);
|
|
21
|
+
|
|
22
|
+
if (!result && sessionId && tabId) {
|
|
23
|
+
const tabKey = `tab:${tabId}`;
|
|
24
|
+
const bootstrap = this.repls?.get(tabKey);
|
|
25
|
+
if (bootstrap) {
|
|
26
|
+
result = promoteBootstrapRuntime(this, originalGet, bootstrap, tabKey, key, sessionId);
|
|
27
|
+
this.repls.set(key, result);
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
if (!result) {
|
|
32
|
+
result = originalGet.call(this, sessionId ? { sessionId } : tabId ? { tabId } : {});
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
touchRuntime(this, key);
|
|
36
|
+
scheduleCleanup(this, key, result, idleMs);
|
|
37
|
+
void enforceSessionLimit(this, maxSessions);
|
|
38
|
+
return result;
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function promoteBootstrapRuntime(router, originalGet, bootstrap, tabKey, sessionKey, sessionId) {
|
|
43
|
+
let promoted;
|
|
44
|
+
promoted = bootstrap.then(async (repl) => {
|
|
45
|
+
if (runtimeSessionId(repl) === sessionId) {
|
|
46
|
+
if (router.repls?.get(tabKey) === bootstrap) router.repls.delete(tabKey);
|
|
47
|
+
clearRuntimeTimer(router, tabKey);
|
|
48
|
+
clearRuntimeAccessTime(router, tabKey);
|
|
49
|
+
return repl;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
if (router.repls?.get(sessionKey) === promoted) router.repls.delete(sessionKey);
|
|
53
|
+
return originalGet.call(router, { sessionId });
|
|
54
|
+
});
|
|
55
|
+
return promoted;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function scheduleCleanup(router, key, replPromise, idleMs) {
|
|
59
|
+
let timers = routerTimers.get(router);
|
|
60
|
+
if (!timers) {
|
|
61
|
+
timers = new Map();
|
|
62
|
+
routerTimers.set(router, timers);
|
|
63
|
+
}
|
|
64
|
+
clearTimeout(timers.get(key));
|
|
65
|
+
const timer = setTimeout(async () => {
|
|
66
|
+
timers.delete(key);
|
|
67
|
+
if (router.repls?.get(key) !== replPromise) return;
|
|
68
|
+
let repl;
|
|
69
|
+
try { repl = await replPromise; } catch { return; }
|
|
70
|
+
if (isRuntimeActive(repl)) {
|
|
71
|
+
scheduleCleanup(router, key, replPromise, idleMs);
|
|
72
|
+
return;
|
|
73
|
+
}
|
|
74
|
+
router.repls.delete(key);
|
|
75
|
+
clearRuntimeAccessTime(router, key);
|
|
76
|
+
}, idleMs);
|
|
77
|
+
timer.unref?.();
|
|
78
|
+
timers.set(key, timer);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
async function enforceSessionLimit(router, maxSessions) {
|
|
82
|
+
const entries = [...(router.repls?.entries() || [])]
|
|
83
|
+
.filter(([key]) => key.startsWith('session:'));
|
|
84
|
+
if (entries.length <= maxSessions) return;
|
|
85
|
+
|
|
86
|
+
const accessTimes = routerAccessTimes.get(router) || new Map();
|
|
87
|
+
entries.sort(([left], [right]) => (accessTimes.get(left) || 0) - (accessTimes.get(right) || 0));
|
|
88
|
+
let overflow = entries.length - maxSessions;
|
|
89
|
+
for (const [key, promise] of entries) {
|
|
90
|
+
if (overflow <= 0) break;
|
|
91
|
+
if (router.repls?.get(key) !== promise) continue;
|
|
92
|
+
let repl;
|
|
93
|
+
try { repl = await promise; } catch { repl = undefined; }
|
|
94
|
+
if (repl && isRuntimeActive(repl)) continue;
|
|
95
|
+
if (router.repls?.get(key) !== promise) continue;
|
|
96
|
+
router.repls.delete(key);
|
|
97
|
+
clearRuntimeTimer(router, key);
|
|
98
|
+
clearRuntimeAccessTime(router, key);
|
|
99
|
+
overflow -= 1;
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
function isRuntimeActive(repl) {
|
|
104
|
+
return Boolean(
|
|
105
|
+
repl?.busy
|
|
106
|
+
|| repl?.subscribers?.size > 0
|
|
107
|
+
|| repl?.backgroundSessionRuns?.size > 0
|
|
108
|
+
|| Number(repl?.backgroundTaskCount || 0) > 0
|
|
109
|
+
);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
function runtimeSessionId(repl) {
|
|
113
|
+
return String(repl?.runtime?.engine?.snapshot?.().session?.sessionId || '').trim();
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
function touchRuntime(router, key) {
|
|
117
|
+
let accessTimes = routerAccessTimes.get(router);
|
|
118
|
+
if (!accessTimes) {
|
|
119
|
+
accessTimes = new Map();
|
|
120
|
+
routerAccessTimes.set(router, accessTimes);
|
|
121
|
+
}
|
|
122
|
+
accessTimes.set(key, Date.now());
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
function clearRuntimeTimer(router, key) {
|
|
126
|
+
const timers = routerTimers.get(router);
|
|
127
|
+
if (!timers) return;
|
|
128
|
+
clearTimeout(timers.get(key));
|
|
129
|
+
timers.delete(key);
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
function clearRuntimeAccessTime(router, key) {
|
|
133
|
+
routerAccessTimes.get(router)?.delete(key);
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
function normalizeScopeValue(value) {
|
|
137
|
+
const normalized = String(value || '').trim();
|
|
138
|
+
return normalized || undefined;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
function positiveNumber(value, fallback) {
|
|
142
|
+
const parsed = Number(value);
|
|
143
|
+
return Number.isFinite(parsed) && parsed > 0 ? parsed : fallback;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
export function runtimeScopeKey(scope = {}) {
|
|
147
|
+
const sessionId = normalizeScopeValue(scope.sessionId);
|
|
148
|
+
if (sessionId) return `session:${sessionId}`;
|
|
149
|
+
const tabId = normalizeScopeValue(scope.tabId);
|
|
150
|
+
if (tabId) return `tab:${tabId}`;
|
|
151
|
+
return '__default__';
|
|
152
|
+
}
|