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.
Files changed (41) hide show
  1. package/CHANGELOG.md +22 -0
  2. package/README.md +1 -1
  3. package/browser-runtime/ai/core-local.mjs +306 -0
  4. package/docs/reference/README.md +20 -0
  5. package/docs/reference/cli.md +9 -1
  6. package/docs/reference/core-native-packaging.md +195 -11
  7. package/docs/reference/inventory/package-api.json +138 -12
  8. package/docs/reference/local-ai.md +197 -0
  9. package/docs/reference/protocols.md +69 -1
  10. package/docs/reference/runtime-modules.md +6 -5
  11. package/docs/reference/sdk-api.md +165 -26
  12. package/package.json +5 -1
  13. package/runtime/arcane/modules/LocalAIReadiness.js +3 -1
  14. package/schemas/arcane-app.schema.json +54 -0
  15. package/schemas/arcane-package.schema.json +44 -0
  16. package/src/app-descriptor.mjs +17 -1
  17. package/src/cli/main.mjs +8 -2
  18. package/src/core/host.mjs +23 -0
  19. package/src/core/hosts/macos/ArcaneCoreProcess.h +85 -0
  20. package/src/core/hosts/macos/ArcaneCoreProcess.m +357 -0
  21. package/src/core/hosts/windows/ArcaneCoreProcess.cs +3 -3
  22. package/src/core/hosts/windows/ArcaneHost.cs +695 -0
  23. package/src/core/hosts/windows/ArcaneHost.manifest +22 -0
  24. package/src/core/hosts/windows/ArcaneLauncher.cs +239 -0
  25. package/src/core/services/local-ai.mjs +543 -0
  26. package/src/dev-server.mjs +7 -0
  27. package/src/import-map.mjs +1 -0
  28. package/src/local-ai/archive.mjs +52 -0
  29. package/src/local-ai/config.mjs +62 -0
  30. package/src/local-ai/dev.mjs +390 -0
  31. package/src/local-ai/discover.mjs +71 -0
  32. package/src/local-ai/http.mjs +106 -0
  33. package/src/local-ai/install.mjs +344 -0
  34. package/src/local-ai/server.mjs +262 -0
  35. package/src/native/portable-layout.mjs +29 -4
  36. package/src/native/portable-provider.mjs +39 -8
  37. package/src/native/windows-provider.mjs +262 -0
  38. package/src/native-provider-loader.mjs +8 -2
  39. package/src/packager/core.mjs +6 -2
  40. package/src/process.mjs +66 -8
  41. 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
+ }