arcane-os 0.56.0 → 0.57.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/CHANGELOG.md +22 -0
- package/README.md +1 -1
- package/browser-runtime/ai/core-local.mjs +306 -0
- package/docs/reference/README.md +20 -0
- package/docs/reference/cli.md +9 -1
- package/docs/reference/core-native-packaging.md +195 -11
- package/docs/reference/inventory/package-api.json +138 -12
- package/docs/reference/local-ai.md +197 -0
- package/docs/reference/protocols.md +69 -1
- package/docs/reference/runtime-modules.md +6 -5
- package/docs/reference/sdk-api.md +165 -26
- package/package.json +5 -1
- package/runtime/arcane/modules/LocalAIReadiness.js +3 -1
- package/schemas/arcane-app.schema.json +54 -0
- package/schemas/arcane-package.schema.json +44 -0
- package/src/app-descriptor.mjs +17 -1
- package/src/cli/main.mjs +8 -2
- package/src/core/host.mjs +23 -0
- package/src/core/hosts/macos/ArcaneCoreProcess.h +85 -0
- package/src/core/hosts/macos/ArcaneCoreProcess.m +357 -0
- package/src/core/hosts/windows/ArcaneCoreProcess.cs +3 -3
- package/src/core/hosts/windows/ArcaneHost.cs +695 -0
- package/src/core/hosts/windows/ArcaneHost.manifest +22 -0
- package/src/core/hosts/windows/ArcaneLauncher.cs +239 -0
- package/src/core/services/local-ai.mjs +543 -0
- package/src/dev-server.mjs +7 -0
- package/src/import-map.mjs +1 -0
- package/src/local-ai/archive.mjs +52 -0
- package/src/local-ai/config.mjs +62 -0
- package/src/local-ai/dev.mjs +390 -0
- package/src/local-ai/discover.mjs +71 -0
- package/src/local-ai/http.mjs +106 -0
- package/src/local-ai/install.mjs +344 -0
- package/src/local-ai/server.mjs +262 -0
- package/src/native/portable-layout.mjs +29 -4
- package/src/native/portable-provider.mjs +39 -8
- package/src/native/windows-provider.mjs +262 -0
- package/src/native-provider-loader.mjs +8 -2
- package/src/packager/core.mjs +6 -2
- package/src/process.mjs +66 -8
- package/src/toolchain.mjs +33 -5
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
<?xml version="1.0" encoding="utf-8"?>
|
|
2
|
+
<assembly manifestVersion="1.0" xmlns="urn:schemas-microsoft-com:asm.v1">
|
|
3
|
+
<assemblyIdentity version="1.0.0.0" name="Arcane.SDK.WindowsHost" type="win32" processorArchitecture="*" />
|
|
4
|
+
<trustInfo xmlns="urn:schemas-microsoft-com:asm.v3">
|
|
5
|
+
<security>
|
|
6
|
+
<requestedPrivileges>
|
|
7
|
+
<requestedExecutionLevel level="asInvoker" uiAccess="false" />
|
|
8
|
+
</requestedPrivileges>
|
|
9
|
+
</security>
|
|
10
|
+
</trustInfo>
|
|
11
|
+
<application xmlns="urn:schemas-microsoft-com:asm.v3">
|
|
12
|
+
<windowsSettings>
|
|
13
|
+
<dpiAware xmlns="http://schemas.microsoft.com/SMI/2005/WindowsSettings">true/pm</dpiAware>
|
|
14
|
+
<dpiAwareness xmlns="http://schemas.microsoft.com/SMI/2016/WindowsSettings">PerMonitorV2,PerMonitor</dpiAwareness>
|
|
15
|
+
</windowsSettings>
|
|
16
|
+
</application>
|
|
17
|
+
<dependency>
|
|
18
|
+
<dependentAssembly>
|
|
19
|
+
<assemblyIdentity type="win32" name="Microsoft.Windows.Common-Controls" version="6.0.0.0" processorArchitecture="*" publicKeyToken="6595b64144ccf1df" language="*" />
|
|
20
|
+
</dependentAssembly>
|
|
21
|
+
</dependency>
|
|
22
|
+
</assembly>
|
|
@@ -0,0 +1,239 @@
|
|
|
1
|
+
using System;
|
|
2
|
+
using System.Collections.Generic;
|
|
3
|
+
using System.Diagnostics;
|
|
4
|
+
using System.IO;
|
|
5
|
+
using System.Runtime.Versioning;
|
|
6
|
+
using System.Text;
|
|
7
|
+
using System.Threading.Tasks;
|
|
8
|
+
using System.Windows.Forms;
|
|
9
|
+
|
|
10
|
+
[assembly: TargetFramework(".NETFramework,Version=v4.6.2", FrameworkDisplayName = ".NET Framework 4.6.2")]
|
|
11
|
+
|
|
12
|
+
namespace Arcane.Core.Hosts.Windows
|
|
13
|
+
{
|
|
14
|
+
/// <summary>The reusable executable entry; application policy stays in its manifest and services.</summary>
|
|
15
|
+
internal static class ArcaneLauncher
|
|
16
|
+
{
|
|
17
|
+
private static TextWriter diagnostics;
|
|
18
|
+
private static string diagnosticFilename;
|
|
19
|
+
private static Exception runnerInputFailure;
|
|
20
|
+
|
|
21
|
+
[STAThread]
|
|
22
|
+
private static int Main(string[] args)
|
|
23
|
+
{
|
|
24
|
+
bool closeOnInputEnd = false;
|
|
25
|
+
try
|
|
26
|
+
{
|
|
27
|
+
string launchFilename = null;
|
|
28
|
+
for (int index = 0; index < args.Length; index++)
|
|
29
|
+
{
|
|
30
|
+
if (args[index] == "--arcane-launch-config")
|
|
31
|
+
{
|
|
32
|
+
if (++index == args.Length || String.IsNullOrEmpty(args[index]))
|
|
33
|
+
throw new ArgumentException("--arcane-launch-config requires a filename.");
|
|
34
|
+
launchFilename = args[index];
|
|
35
|
+
}
|
|
36
|
+
else if (args[index] == "--close-on-stdin-eof") closeOnInputEnd = true;
|
|
37
|
+
else throw new ArgumentException("Unknown launcher argument: " + args[index]);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// A GUI executable has no console encoding handle. The SDK
|
|
41
|
+
// runner can still redirect stderr; write UTF-8 to that stream
|
|
42
|
+
// directly without changing a nonexistent console code page.
|
|
43
|
+
StreamWriter standardError = new StreamWriter(Console.OpenStandardError(), new UTF8Encoding(false));
|
|
44
|
+
standardError.AutoFlush = true;
|
|
45
|
+
Console.SetError(TextWriter.Synchronized(standardError));
|
|
46
|
+
|
|
47
|
+
string directory = AppDomain.CurrentDomain.BaseDirectory;
|
|
48
|
+
Dictionary<string, object> manifest = ReadRecord(Path.Combine(directory, "arcane-native.json"));
|
|
49
|
+
Dictionary<string, object> app = RequiredRecord(manifest, "app");
|
|
50
|
+
Dictionary<string, object> client = RequiredRecord(manifest, "client");
|
|
51
|
+
string appId = RequiredString(app, "id");
|
|
52
|
+
object title;
|
|
53
|
+
string name = app.TryGetValue("displayName", out title) && title is string ? (string)title : appId;
|
|
54
|
+
|
|
55
|
+
// Core rereads this exact selected file. Do not normalize,
|
|
56
|
+
// rewrite, filter or copy the application's launch record.
|
|
57
|
+
Dictionary<string, object> launchContext = launchFilename == null ? null : ReadRecord(launchFilename);
|
|
58
|
+
object stateRoot;
|
|
59
|
+
string profileParent = launchContext != null && launchContext.TryGetValue("stateRoot", out stateRoot) && stateRoot is string
|
|
60
|
+
? Path.GetFullPath((string)stateRoot)
|
|
61
|
+
: Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), "Arcane", appId);
|
|
62
|
+
OpenDiagnostics(Path.Combine(profileParent, "Diagnostics"));
|
|
63
|
+
ArcaneHostOptions options = new ArcaneHostOptions
|
|
64
|
+
{
|
|
65
|
+
ApplicationRoot = Path.GetFullPath(Path.Combine(directory, RequiredString(manifest, "webRoot"))),
|
|
66
|
+
StartPath = RequiredString(manifest, "start"),
|
|
67
|
+
OriginHost = "arcane.local",
|
|
68
|
+
ProfileDirectory = Path.Combine(profileParent, "WebView2"),
|
|
69
|
+
Title = name,
|
|
70
|
+
ClassicClientSource = File.ReadAllText(Path.Combine(directory, RequiredString(client, "source"))),
|
|
71
|
+
CoreExecutable = Path.Combine(directory, "runtime", "ArcaneCore.exe"),
|
|
72
|
+
CoreArguments = launchFilename == null ? String.Empty : "--arcane-launch-config " + QuoteArgument(launchFilename),
|
|
73
|
+
// Relative explicit launch filenames retain their caller's
|
|
74
|
+
// meaning; the SEA locates its loader beside its executable.
|
|
75
|
+
CoreWorkingDirectory = Environment.CurrentDirectory
|
|
76
|
+
};
|
|
77
|
+
|
|
78
|
+
Application.EnableVisualStyles();
|
|
79
|
+
Application.SetCompatibleTextRenderingDefault(false);
|
|
80
|
+
using (ArcaneHostForm window = new ArcaneHostForm(options, WriteDiagnostic, WriteError))
|
|
81
|
+
{
|
|
82
|
+
Task inputLifetime = null;
|
|
83
|
+
if (closeOnInputEnd)
|
|
84
|
+
{
|
|
85
|
+
// Only the SDK runner opts into stdin lifetime. Starting
|
|
86
|
+
// after Shown gives CloseAsync its actual UI handle; the
|
|
87
|
+
// default double-click path never reads or waits on stdin.
|
|
88
|
+
window.Shown += delegate
|
|
89
|
+
{
|
|
90
|
+
if (inputLifetime != null) return;
|
|
91
|
+
inputLifetime = CloseOnInputEndAsync(window);
|
|
92
|
+
inputLifetime.ContinueWith(ObserveInputFailure, TaskContinuationOptions.OnlyOnFaulted);
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
Application.Run(window);
|
|
96
|
+
// The host keeps its message loop alive for the accepted
|
|
97
|
+
// Core work and transport drain before FormClosed.
|
|
98
|
+
window.Completion.GetAwaiter().GetResult();
|
|
99
|
+
if (runnerInputFailure != null) throw new IOException("The runner's input lifetime failed.", runnerInputFailure);
|
|
100
|
+
if (inputLifetime != null && inputLifetime.IsFaulted) throw inputLifetime.Exception;
|
|
101
|
+
GC.KeepAlive(inputLifetime);
|
|
102
|
+
}
|
|
103
|
+
GC.KeepAlive(launchContext);
|
|
104
|
+
return 0;
|
|
105
|
+
}
|
|
106
|
+
catch (Exception error)
|
|
107
|
+
{
|
|
108
|
+
try
|
|
109
|
+
{
|
|
110
|
+
// An unreadable manifest cannot supply an app identity.
|
|
111
|
+
// Retain that early failure in the launcher's diagnostic
|
|
112
|
+
// directory without inventing an application's namespace.
|
|
113
|
+
if (diagnostics == null)
|
|
114
|
+
OpenDiagnostics(Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), "Arcane", "Diagnostics"));
|
|
115
|
+
WriteError(error);
|
|
116
|
+
}
|
|
117
|
+
catch (Exception diagnosticError)
|
|
118
|
+
{
|
|
119
|
+
Console.Error.WriteLine(new AggregateException("The application and diagnostic output failed.", error, diagnosticError));
|
|
120
|
+
Trace.WriteLine(error);
|
|
121
|
+
Trace.WriteLine(diagnosticError);
|
|
122
|
+
}
|
|
123
|
+
if (!closeOnInputEnd)
|
|
124
|
+
MessageBox.Show("The application encountered an error."
|
|
125
|
+
+ (diagnosticFilename == null ? " A diagnostic file could not be opened." : "\n\nDetails: " + diagnosticFilename),
|
|
126
|
+
"Arcane", MessageBoxButtons.OK, MessageBoxIcon.Error);
|
|
127
|
+
return 1;
|
|
128
|
+
}
|
|
129
|
+
finally
|
|
130
|
+
{
|
|
131
|
+
if (diagnostics != null) diagnostics.Dispose();
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
private static Dictionary<string, object> ReadRecord(string filename)
|
|
136
|
+
{
|
|
137
|
+
object value = ArcaneHost.Serializer().DeserializeObject(File.ReadAllText(filename));
|
|
138
|
+
Dictionary<string, object> record = value as Dictionary<string, object>;
|
|
139
|
+
if (record == null) throw new FormatException("Expected a JSON object in " + filename + ".");
|
|
140
|
+
return record;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
private static Dictionary<string, object> RequiredRecord(Dictionary<string, object> source, string name)
|
|
144
|
+
{
|
|
145
|
+
object value;
|
|
146
|
+
Dictionary<string, object> record = source.TryGetValue(name, out value) ? value as Dictionary<string, object> : null;
|
|
147
|
+
if (record == null) throw new FormatException("The native manifest needs an object at " + name + ".");
|
|
148
|
+
return record;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
private static string RequiredString(Dictionary<string, object> source, string name)
|
|
152
|
+
{
|
|
153
|
+
object value;
|
|
154
|
+
string text = source.TryGetValue(name, out value) ? value as string : null;
|
|
155
|
+
if (String.IsNullOrEmpty(text)) throw new FormatException("The native manifest needs a string at " + name + ".");
|
|
156
|
+
return text;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
private static string QuoteArgument(string value)
|
|
160
|
+
{
|
|
161
|
+
// Windows command-line escaping is transport-local. The child
|
|
162
|
+
// receives the complete original filename, including backslashes.
|
|
163
|
+
StringBuilder quoted = new StringBuilder("\"");
|
|
164
|
+
int slashes = 0;
|
|
165
|
+
foreach (char character in value)
|
|
166
|
+
{
|
|
167
|
+
if (character == '\\') { slashes++; continue; }
|
|
168
|
+
if (character == '"')
|
|
169
|
+
{
|
|
170
|
+
quoted.Append('\\', slashes * 2 + 1);
|
|
171
|
+
quoted.Append('"');
|
|
172
|
+
}
|
|
173
|
+
else
|
|
174
|
+
{
|
|
175
|
+
quoted.Append('\\', slashes);
|
|
176
|
+
quoted.Append(character);
|
|
177
|
+
}
|
|
178
|
+
slashes = 0;
|
|
179
|
+
}
|
|
180
|
+
quoted.Append('\\', slashes * 2);
|
|
181
|
+
quoted.Append('"');
|
|
182
|
+
return quoted.ToString();
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
private static async Task CloseOnInputEndAsync(ArcaneHostForm window)
|
|
186
|
+
{
|
|
187
|
+
// Standard input is an optional process-lifetime signal, not an
|
|
188
|
+
// application message channel. Its blocking read stays off the UI
|
|
189
|
+
// thread. Closing the window never waits for an open parent's pipe.
|
|
190
|
+
try
|
|
191
|
+
{
|
|
192
|
+
await Task.Run(delegate
|
|
193
|
+
{
|
|
194
|
+
using (Stream input = Console.OpenStandardInput())
|
|
195
|
+
{
|
|
196
|
+
byte[] buffer = new byte[4096];
|
|
197
|
+
while (input.Read(buffer, 0, buffer.Length) != 0) { }
|
|
198
|
+
}
|
|
199
|
+
}).ConfigureAwait(false);
|
|
200
|
+
}
|
|
201
|
+
catch (Exception error) { runnerInputFailure = error; }
|
|
202
|
+
await window.CloseAsync().ConfigureAwait(false);
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
private static void ObserveInputFailure(Task failure)
|
|
206
|
+
{
|
|
207
|
+
// Main reports the same window-completion failure and any input
|
|
208
|
+
// failure after the graceful drain. Observation itself cannot throw.
|
|
209
|
+
failure.Exception.Handle(error => true);
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
private static void WriteDiagnostic(string text)
|
|
213
|
+
{
|
|
214
|
+
diagnostics.Write(text);
|
|
215
|
+
diagnostics.Flush();
|
|
216
|
+
Console.Error.Write(text);
|
|
217
|
+
Trace.Write(text);
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
private static void WriteError(Exception error)
|
|
221
|
+
{
|
|
222
|
+
string diagnostic = ArcaneHost.Serializer().Serialize(ArcaneHost.ErrorRecord(error, "ARCANE_WINDOWS_HOST_FAILED"));
|
|
223
|
+
diagnostics.WriteLine(diagnostic);
|
|
224
|
+
diagnostics.Flush();
|
|
225
|
+
Console.Error.WriteLine(diagnostic);
|
|
226
|
+
Trace.WriteLine(diagnostic);
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
private static void OpenDiagnostics(string directory)
|
|
230
|
+
{
|
|
231
|
+
Directory.CreateDirectory(directory);
|
|
232
|
+
string filename = Path.Combine(directory, "launch-" + DateTime.UtcNow.ToString("yyyyMMddTHHmmss") + "-" + Guid.NewGuid().ToString("N") + ".log");
|
|
233
|
+
StreamWriter writer = new StreamWriter(new FileStream(filename, FileMode.CreateNew, FileAccess.Write, FileShare.Read), new UTF8Encoding(false));
|
|
234
|
+
writer.AutoFlush = true;
|
|
235
|
+
diagnostics = TextWriter.Synchronized(writer);
|
|
236
|
+
diagnosticFilename = filename;
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
}
|