neoctl-web 0.1.6 → 0.1.8
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 +10 -10
- package/bin/neow.mjs +28 -28
- package/control-protocol.mjs +44 -0
- package/control-sync.mjs +326 -0
- package/dist/assets/index-BihhJN9D.js +122 -0
- package/dist/assets/{index-MgmD3vMN.css → index-CIm8rv9k.css} +1 -1
- package/dist/index.html +2 -2
- package/package.json +4 -1
- package/platform-paths.mjs +40 -40
- package/server.mjs +56 -17
- package/dist/assets/index-CwSgoMSk.js +0 -122
package/dist/index.html
CHANGED
|
@@ -19,8 +19,8 @@
|
|
|
19
19
|
})()
|
|
20
20
|
</script>
|
|
21
21
|
<title>neo runtime</title>
|
|
22
|
-
<script type="module" crossorigin src="/assets/index-
|
|
23
|
-
<link rel="stylesheet" crossorigin href="/assets/index-
|
|
22
|
+
<script type="module" crossorigin src="/assets/index-BihhJN9D.js"></script>
|
|
23
|
+
<link rel="stylesheet" crossorigin href="/assets/index-CIm8rv9k.css">
|
|
24
24
|
</head>
|
|
25
25
|
<body>
|
|
26
26
|
<div id="app"></div>
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "neoctl-web",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.8",
|
|
4
4
|
"description": "Neo browser workspace with an embedded agent runtime.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"engines": {
|
|
@@ -15,6 +15,7 @@
|
|
|
15
15
|
"test:runtime": "node --test runtime-router-cleanup.test.mjs runtime-workspaces.test.mjs",
|
|
16
16
|
"test:plugins": "npm --prefix ../engine run build && node --test plugins.test.mjs plugin-settings.test.mjs tool-settings.test.mjs",
|
|
17
17
|
"test:monitoring": "node --test cpa-quota.test.mjs memory-monitor.test.mjs",
|
|
18
|
+
"test:control": "node --test control-sync.test.mjs control-transcript.test.mjs",
|
|
18
19
|
"build": "vite build",
|
|
19
20
|
"test:cli": "node --test neow.test.mjs",
|
|
20
21
|
"prestart": "npm run build",
|
|
@@ -46,6 +47,8 @@
|
|
|
46
47
|
"dist",
|
|
47
48
|
"plugins",
|
|
48
49
|
"server.mjs",
|
|
50
|
+
"control-sync.mjs",
|
|
51
|
+
"control-protocol.mjs",
|
|
49
52
|
"core-runtime.mjs",
|
|
50
53
|
"plugins.mjs",
|
|
51
54
|
"plugin-settings.mjs",
|
package/platform-paths.mjs
CHANGED
|
@@ -1,40 +1,40 @@
|
|
|
1
|
-
import os from 'node:os';
|
|
2
|
-
import path from 'node:path';
|
|
3
|
-
|
|
4
|
-
export function defaultWebDataRoot(options = {}) {
|
|
5
|
-
const platform = options.platform || process.platform;
|
|
6
|
-
const env = options.env || process.env;
|
|
7
|
-
const homeDir = options.homeDir || os.homedir();
|
|
8
|
-
const pathApi = platform === 'win32' ? path.win32 : path.posix;
|
|
9
|
-
|
|
10
|
-
if (platform === 'win32') {
|
|
11
|
-
const localAppData = absoluteEnvPath(env.LOCALAPPDATA, pathApi);
|
|
12
|
-
return pathApi.join(localAppData || pathApi.join(homeDir, 'AppData', 'Local'), 'neoctl-web');
|
|
13
|
-
}
|
|
14
|
-
if (platform === 'darwin') {
|
|
15
|
-
return pathApi.join(homeDir, 'Library', 'Application Support', 'neoctl-web');
|
|
16
|
-
}
|
|
17
|
-
const xdgDataHome = absoluteEnvPath(env.XDG_DATA_HOME, pathApi);
|
|
18
|
-
return pathApi.join(xdgDataHome || pathApi.join(homeDir, '.local', 'share'), 'neoctl-web');
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
export function resolveWebStorage(options = {}) {
|
|
22
|
-
const platform = options.platform || process.platform;
|
|
23
|
-
const env = options.env || process.env;
|
|
24
|
-
const pathApi = platform === 'win32' ? path.win32 : path.posix;
|
|
25
|
-
const cwd = options.cwd || process.cwd();
|
|
26
|
-
const dataRoot = pathApi.resolve(
|
|
27
|
-
cwd,
|
|
28
|
-
String(env.NEO_WEB_DATA_DIR || '').trim() || defaultWebDataRoot({ ...options, platform, env }),
|
|
29
|
-
);
|
|
30
|
-
const workspaceRoot = pathApi.resolve(
|
|
31
|
-
cwd,
|
|
32
|
-
String(env.NEO_WORKSPACE_ROOT || '').trim() || pathApi.join(dataRoot, 'workspaces'),
|
|
33
|
-
);
|
|
34
|
-
return { dataRoot, workspaceRoot };
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
function absoluteEnvPath(value, pathApi) {
|
|
38
|
-
const candidate = String(value || '').trim();
|
|
39
|
-
return candidate && pathApi.isAbsolute(candidate) ? candidate : '';
|
|
40
|
-
}
|
|
1
|
+
import os from 'node:os';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
|
|
4
|
+
export function defaultWebDataRoot(options = {}) {
|
|
5
|
+
const platform = options.platform || process.platform;
|
|
6
|
+
const env = options.env || process.env;
|
|
7
|
+
const homeDir = options.homeDir || os.homedir();
|
|
8
|
+
const pathApi = platform === 'win32' ? path.win32 : path.posix;
|
|
9
|
+
|
|
10
|
+
if (platform === 'win32') {
|
|
11
|
+
const localAppData = absoluteEnvPath(env.LOCALAPPDATA, pathApi);
|
|
12
|
+
return pathApi.join(localAppData || pathApi.join(homeDir, 'AppData', 'Local'), 'neoctl-web');
|
|
13
|
+
}
|
|
14
|
+
if (platform === 'darwin') {
|
|
15
|
+
return pathApi.join(homeDir, 'Library', 'Application Support', 'neoctl-web');
|
|
16
|
+
}
|
|
17
|
+
const xdgDataHome = absoluteEnvPath(env.XDG_DATA_HOME, pathApi);
|
|
18
|
+
return pathApi.join(xdgDataHome || pathApi.join(homeDir, '.local', 'share'), 'neoctl-web');
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export function resolveWebStorage(options = {}) {
|
|
22
|
+
const platform = options.platform || process.platform;
|
|
23
|
+
const env = options.env || process.env;
|
|
24
|
+
const pathApi = platform === 'win32' ? path.win32 : path.posix;
|
|
25
|
+
const cwd = options.cwd || process.cwd();
|
|
26
|
+
const dataRoot = pathApi.resolve(
|
|
27
|
+
cwd,
|
|
28
|
+
String(env.NEO_WEB_DATA_DIR || '').trim() || defaultWebDataRoot({ ...options, platform, env }),
|
|
29
|
+
);
|
|
30
|
+
const workspaceRoot = pathApi.resolve(
|
|
31
|
+
cwd,
|
|
32
|
+
String(env.NEO_WORKSPACE_ROOT || '').trim() || pathApi.join(dataRoot, 'workspaces'),
|
|
33
|
+
);
|
|
34
|
+
return { dataRoot, workspaceRoot };
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function absoluteEnvPath(value, pathApi) {
|
|
38
|
+
const candidate = String(value || '').trim();
|
|
39
|
+
return candidate && pathApi.isAbsolute(candidate) ? candidate : '';
|
|
40
|
+
}
|
package/server.mjs
CHANGED
|
@@ -3,16 +3,30 @@ import fs from 'node:fs';
|
|
|
3
3
|
import fsp from 'node:fs/promises';
|
|
4
4
|
import path from 'node:path';
|
|
5
5
|
import { fileURLToPath } from 'node:url';
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
6
|
+
// Consume the private launcher secret before importing application/runtime modules.
|
|
7
|
+
// Imports can initialize plugins or spawn children; none may inherit this value.
|
|
8
|
+
let desktopControlConfig;
|
|
9
|
+
{
|
|
10
|
+
const raw = process.env.NEO_DESKTOP_CONTROL_CONFIG;
|
|
11
|
+
delete process.env.NEO_DESKTOP_CONTROL_CONFIG;
|
|
12
|
+
try { desktopControlConfig = raw ? JSON.parse(raw) : undefined; } catch {}
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
const { coreRuntimeInfo, createWebRuntime, loadNeoPlugins, runWebServer } = await import('./core-runtime.mjs');
|
|
16
|
+
const { createWebPluginHost } = await import('./plugins.mjs');
|
|
17
|
+
const { createWebPluginSettings } = await import('./plugin-settings.mjs');
|
|
18
|
+
const { createWebToolSettings } = await import('./tool-settings.mjs');
|
|
19
|
+
const { createWorkspaceRuntimeManager } = await import('./runtime-workspaces.mjs');
|
|
20
|
+
const { installRuntimeRouterIdleCleanup } = await import('./runtime-router-cleanup.mjs');
|
|
21
|
+
const { createCpaQuotaMonitor } = await import('./cpa-quota.mjs');
|
|
22
|
+
const { createMemoryMonitor } = await import('./memory-monitor.mjs');
|
|
23
|
+
const { resolveWebStorage } = await import('./platform-paths.mjs');
|
|
24
|
+
const { createControlSync, createLoginApplier, validateControlConfig } = await import('./control-sync.mjs');
|
|
15
25
|
|
|
26
|
+
const controlConfig = validateControlConfig(desktopControlConfig);
|
|
27
|
+
desktopControlConfig = undefined;
|
|
28
|
+
const controlEnabled = Boolean(controlConfig);
|
|
29
|
+
|
|
16
30
|
installRuntimeRouterIdleCleanup();
|
|
17
31
|
console.log(`neo core: ${coreRuntimeInfo.source} ${coreRuntimeInfo.version} (${coreRuntimeInfo.location})`);
|
|
18
32
|
process.env.NEO_CORE_VERSION = coreRuntimeInfo.version;
|
|
@@ -21,9 +35,9 @@ process.env.NEO_CLIENT_REVISION ||= `${coreRuntimeInfo.version}-${Date.now().toS
|
|
|
21
35
|
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
22
36
|
const root = path.resolve(process.env.DIST_DIR || path.join(__dirname, 'dist'));
|
|
23
37
|
const host = process.env.APP_HOST || '0.0.0.0';
|
|
24
|
-
const port = Number(process.env.APP_PORT || process.env.PORT || 5173);
|
|
25
|
-
const runtimeTarget = new URL(process.env.NEO_RUNTIME_TARGET || 'http://127.0.0.1:3101');
|
|
26
|
-
const { dataRoot, workspaceRoot } = resolveWebStorage();
|
|
38
|
+
const port = Number(process.env.APP_PORT || process.env.PORT || 5173);
|
|
39
|
+
const runtimeTarget = new URL(process.env.NEO_RUNTIME_TARGET || 'http://127.0.0.1:3101');
|
|
40
|
+
const { dataRoot, workspaceRoot } = resolveWebStorage();
|
|
27
41
|
const promptLibraryFile = path.resolve(process.env.NEO_PROMPT_LIBRARY_FILE || path.join(dataRoot, 'prompt-library.json'));
|
|
28
42
|
const uploadsDir = path.resolve(process.env.NEO_UPLOADS_DIR || path.join(dataRoot, 'uploads'));
|
|
29
43
|
const pluginDir = path.resolve(process.env.NEO_WEB_PLUGIN_DIR || path.join(__dirname, 'plugins'));
|
|
@@ -53,10 +67,21 @@ const memoryMonitor = createMemoryMonitor({
|
|
|
53
67
|
maxPersistedSamples: process.env.NEO_MEMORY_MAX_PERSISTED_SAMPLES,
|
|
54
68
|
maxPersistedBytes: process.env.NEO_MEMORY_MAX_PERSISTED_BYTES,
|
|
55
69
|
});
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
70
|
+
// Weak references do not defeat the existing router's idle-session cleanup.
|
|
71
|
+
const controlRepls = new Set();
|
|
72
|
+
function activeControlRepls() {
|
|
73
|
+
const active = [];
|
|
74
|
+
for (const reference of controlRepls) {
|
|
75
|
+
const repl = reference.deref();
|
|
76
|
+
if (repl) active.push(repl);
|
|
77
|
+
else controlRepls.delete(reference);
|
|
78
|
+
}
|
|
79
|
+
return active;
|
|
80
|
+
}
|
|
81
|
+
const workspaceRuntime = createWorkspaceRuntimeManager({
|
|
82
|
+
projectRoot: process.cwd(),
|
|
83
|
+
workspaceRoot,
|
|
84
|
+
registryFile: path.join(dataRoot, 'session-workspaces.json'),
|
|
60
85
|
createRuntime: (runtimeOptions) => createWebRuntime({
|
|
61
86
|
...runtimeOptions,
|
|
62
87
|
...pluginHost.runtimePlugins(runtimeOptions.sessionId),
|
|
@@ -106,12 +131,26 @@ await new Promise((resolve, reject) => {
|
|
|
106
131
|
});
|
|
107
132
|
});
|
|
108
133
|
|
|
134
|
+
// Optional Desktop-only background work starts after HTTP listen and never delays UI.
|
|
135
|
+
if (controlEnabled) {
|
|
136
|
+
const controlSync = createControlSync({
|
|
137
|
+
config: controlConfig,
|
|
138
|
+
dataDir: dataRoot,
|
|
139
|
+
applyProfile: embedRuntime ? createLoginApplier({ runtimeUrl: runtimeTarget, getActiveRepls: activeControlRepls }) : undefined,
|
|
140
|
+
}).start();
|
|
141
|
+
server.once('close', () => { void controlSync.stop(); });
|
|
142
|
+
}
|
|
143
|
+
|
|
109
144
|
async function startEmbeddedRuntime() {
|
|
110
145
|
const runtimeHost = runtimeTarget.hostname || '127.0.0.1';
|
|
111
146
|
const runtimePort = runtimeTarget.port || '3101';
|
|
112
147
|
await runWebServer(['--host', runtimeHost, '--port', runtimePort], {
|
|
113
148
|
createRuntime: workspaceRuntime.createRuntime,
|
|
114
|
-
createRepl
|
|
149
|
+
createRepl(runtime) {
|
|
150
|
+
const repl = workspaceRuntime.createRepl(runtime);
|
|
151
|
+
if (controlEnabled) controlRepls.add(new WeakRef(repl));
|
|
152
|
+
return repl;
|
|
153
|
+
},
|
|
115
154
|
});
|
|
116
155
|
}
|
|
117
156
|
|