arcane-os 0.56.0 → 0.58.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 (75) hide show
  1. package/CHANGELOG.md +43 -0
  2. package/README.md +6 -2
  3. package/browser-runtime/ai/core-local.mjs +306 -0
  4. package/browser-runtime/ai/core-onnx.mjs +96 -0
  5. package/browser-runtime/ai/onnx-tensors.mjs +85 -0
  6. package/browser-runtime/core/client.mjs +151 -16
  7. package/docs/architecture.md +5 -2
  8. package/docs/platform-targets.md +30 -13
  9. package/docs/reference/README.md +38 -3
  10. package/docs/reference/availability-and-normalization.md +9 -6
  11. package/docs/reference/cli.md +29 -9
  12. package/docs/reference/core-client.md +80 -0
  13. package/docs/reference/core-native-packaging.md +304 -12
  14. package/docs/reference/core-preferences.md +49 -0
  15. package/docs/reference/diarization.md +206 -0
  16. package/docs/reference/git-text-snapshot.md +104 -0
  17. package/docs/reference/inventory/package-api.json +330 -22
  18. package/docs/reference/local-ai.md +331 -0
  19. package/docs/reference/protocols.md +97 -20
  20. package/docs/reference/pwa.md +5 -2
  21. package/docs/reference/runtime-modules.md +6 -5
  22. package/docs/reference/sdk-api.md +611 -63
  23. package/package.json +15 -3
  24. package/runtime/arcane/modules/LocalAIReadiness.js +3 -1
  25. package/schemas/arcane-app.schema.json +55 -1
  26. package/schemas/arcane-package.schema.json +44 -0
  27. package/schemas/native-build-plan.schema.json +27 -1
  28. package/schemas/target-adapter.schema.json +2 -0
  29. package/src/app-descriptor.mjs +18 -2
  30. package/src/cli/main.mjs +20 -4
  31. package/src/constants.mjs +2 -0
  32. package/src/core/host.mjs +23 -0
  33. package/src/core/hosts/macos/ArcaneCoreProcess.h +85 -0
  34. package/src/core/hosts/macos/ArcaneCoreProcess.m +357 -0
  35. package/src/core/hosts/macos/ArcaneHost.h +86 -0
  36. package/src/core/hosts/macos/ArcaneHost.m +455 -0
  37. package/src/core/hosts/macos/ArcaneLauncher.m +432 -0
  38. package/src/core/hosts/windows/ArcaneCoreProcess.cs +3 -3
  39. package/src/core/hosts/windows/ArcaneHost.cs +695 -0
  40. package/src/core/hosts/windows/ArcaneHost.manifest +22 -0
  41. package/src/core/hosts/windows/ArcaneLauncher.cs +239 -0
  42. package/src/core/services/local-ai.mjs +621 -0
  43. package/src/core/services/packaged-web.mjs +89 -0
  44. package/src/core/services/preferences.mjs +201 -0
  45. package/src/core-classic-source.mjs +2 -2
  46. package/src/dev-server.mjs +9 -5
  47. package/src/diarization/build.mjs +35 -0
  48. package/src/diarization/index.mjs +432 -0
  49. package/src/diarization/native/CMakeLists.txt +9 -0
  50. package/src/diarization/native/main.cpp +269 -0
  51. package/src/diarization/service.mjs +129 -0
  52. package/src/git-text-snapshot.mjs +265 -0
  53. package/src/import-map.mjs +2 -0
  54. package/src/index.mjs +1 -0
  55. package/src/local-ai/archive.mjs +52 -0
  56. package/src/local-ai/config.mjs +62 -0
  57. package/src/local-ai/dev.mjs +390 -0
  58. package/src/local-ai/discover.mjs +71 -0
  59. package/src/local-ai/http.mjs +106 -0
  60. package/src/local-ai/install.mjs +459 -0
  61. package/src/local-ai/onnx-worker.mjs +85 -0
  62. package/src/local-ai/onnx.mjs +368 -0
  63. package/src/local-ai/server.mjs +262 -0
  64. package/src/native/macos-provider.mjs +291 -0
  65. package/src/native/portable-layout.mjs +37 -4
  66. package/src/native/portable-provider.mjs +44 -8
  67. package/src/native/windows-provider.mjs +262 -0
  68. package/src/native-plan.mjs +9 -1
  69. package/src/native-provider-loader.mjs +10 -2
  70. package/src/packager/core.mjs +6 -2
  71. package/src/process.mjs +184 -20
  72. package/src/scaffold.mjs +1 -1
  73. package/src/targets/index.mjs +11 -1
  74. package/src/templates/workspace-template.mjs +6 -5
  75. package/src/toolchain.mjs +33 -5
@@ -0,0 +1,357 @@
1
+ #import "ArcaneCoreProcess.h"
2
+ #import <dispatch/dispatch.h>
3
+ #import <errno.h>
4
+ #import <fcntl.h>
5
+
6
+ static NSError *ArcaneProcessError(NSString *code, NSString *message,
7
+ NSString *operation, NSError *underlying) {
8
+ NSMutableDictionary *details = [@{NSLocalizedDescriptionKey: message,
9
+ @"code": code, @"operation": operation} mutableCopy];
10
+ if (underlying) details[NSUnderlyingErrorKey] = underlying;
11
+ return [NSError errorWithDomain:@"ArcaneCoreProcess" code:1 userInfo:details];
12
+ }
13
+
14
+ static NSError *ArcaneProcessException(NSException *exception, NSString *operation) {
15
+ return [NSError errorWithDomain:@"ArcaneCoreProcess" code:1 userInfo:@{
16
+ NSLocalizedDescriptionKey: exception.reason ?: exception.name,
17
+ @"code": @"CORE_PROCESS_EXCEPTION", @"operation": operation,
18
+ @"exceptionName": exception.name, @"exceptionInfo": exception.userInfo ?: @{},
19
+ @"callStackSymbols": exception.callStackSymbols
20
+ }];
21
+ }
22
+
23
+ @interface ArcaneCoreProcess () {
24
+ NSURL *_executableURL;
25
+ NSArray<NSString *> *_arguments;
26
+ NSURL *_workingDirectoryURL;
27
+ id<ArcaneCoreProcessDelegate> _delegate;
28
+ NSLock *_state;
29
+ NSMutableArray<NSError *> *_errors;
30
+ dispatch_queue_t _inputQueue;
31
+ dispatch_group_t _lifetime;
32
+ NSTask *_task;
33
+ NSFileHandle *_input;
34
+ NSError *_inputFailure;
35
+ BOOL _accepting;
36
+ BOOL _closeQueued;
37
+ pid_t _processIdentifier;
38
+ }
39
+ - (instancetype)initExecutableURL:(NSURL *)executableURL
40
+ arguments:(NSArray<NSString *> *)arguments
41
+ workingDirectoryURL:(NSURL *)workingDirectoryURL
42
+ delegate:(id<ArcaneCoreProcessDelegate>)delegate;
43
+ @end
44
+
45
+ @implementation ArcaneCoreProcess
46
+
47
+ + (instancetype)startExecutableURL:(NSURL *)executableURL
48
+ arguments:(NSArray<NSString *> *)arguments
49
+ workingDirectoryURL:(NSURL *)workingDirectoryURL
50
+ delegate:(id<ArcaneCoreProcessDelegate>)delegate {
51
+ if (!executableURL || !arguments || !workingDirectoryURL || !delegate) {
52
+ [NSException raise:NSInvalidArgumentException
53
+ format:@"Select a Core executable, arguments, working directory and delegate."];
54
+ }
55
+ ArcaneCoreProcess *process = [[self alloc] initExecutableURL:executableURL
56
+ arguments:arguments workingDirectoryURL:workingDirectoryURL delegate:delegate];
57
+ [process start];
58
+ return process;
59
+ }
60
+
61
+ - (instancetype)initExecutableURL:(NSURL *)executableURL
62
+ arguments:(NSArray<NSString *> *)arguments
63
+ workingDirectoryURL:(NSURL *)workingDirectoryURL
64
+ delegate:(id<ArcaneCoreProcessDelegate>)delegate {
65
+ self = [super init];
66
+ if (self) {
67
+ _executableURL = [executableURL copy];
68
+ _arguments = [[NSArray alloc] initWithArray:arguments copyItems:YES];
69
+ _workingDirectoryURL = [workingDirectoryURL copy];
70
+ _delegate = delegate;
71
+ _state = [[NSLock alloc] init];
72
+ _errors = [NSMutableArray array];
73
+ _inputQueue = dispatch_queue_create("arcane.core.macos.input", DISPATCH_QUEUE_SERIAL);
74
+ _lifetime = dispatch_group_create();
75
+ _accepting = YES;
76
+ }
77
+ return self;
78
+ }
79
+
80
+ - (pid_t)processIdentifier {
81
+ [_state lock];
82
+ pid_t result = _processIdentifier;
83
+ [_state unlock];
84
+ return result;
85
+ }
86
+
87
+ - (void)reportError:(NSError *)error {
88
+ [_state lock];
89
+ [_errors addObject:error];
90
+ [_state unlock];
91
+ [_delegate coreProcess:self didEncounterError:error];
92
+ }
93
+
94
+ - (void)start {
95
+ // Startup and stdin each retain ownership until their actual completion.
96
+ dispatch_group_enter(_lifetime);
97
+ dispatch_group_enter(_lifetime);
98
+ dispatch_async(_inputQueue, ^{ [self launch]; });
99
+ dispatch_group_notify(_lifetime, dispatch_get_global_queue(QOS_CLASS_UTILITY, 0), ^{
100
+ [self complete];
101
+ });
102
+ }
103
+
104
+ - (void)launch {
105
+ @autoreleasepool {
106
+ NSPipe *inputPipe = nil;
107
+ NSPipe *outputPipe = nil;
108
+ NSPipe *errorPipe = nil;
109
+ NSError *launchError = nil;
110
+ BOOL launched = NO;
111
+ @try {
112
+ inputPipe = [NSPipe pipe];
113
+ outputPipe = [NSPipe pipe];
114
+ errorPipe = [NSPipe pipe];
115
+ _input = inputPipe.fileHandleForWriting;
116
+ _task = [[NSTask alloc] init];
117
+ _task.executableURL = _executableURL;
118
+ _task.arguments = _arguments;
119
+ _task.currentDirectoryURL = _workingDirectoryURL;
120
+ _task.standardInput = inputPipe;
121
+ _task.standardOutput = outputPipe;
122
+ _task.standardError = errorPipe;
123
+ // A closed child pipe must produce an observable EPIPE write error,
124
+ // not terminate the host. This affects this descriptor only.
125
+ if (fcntl(_input.fileDescriptor, F_SETNOSIGPIPE, 1) == -1) {
126
+ launchError = [NSError errorWithDomain:NSPOSIXErrorDomain code:errno userInfo:nil];
127
+ } else {
128
+ launched = [_task launchAndReturnError:&launchError];
129
+ }
130
+ }
131
+ @catch (NSException *exception) { launchError = ArcaneProcessException(exception, @"launch"); }
132
+ if (!launched) {
133
+ _inputFailure = ArcaneProcessError(@"CORE_PROCESS_START_FAILED",
134
+ @"The selected Core executable did not start.", @"launch", launchError);
135
+ [self reportError:_inputFailure];
136
+ [self closeInput];
137
+ // No child owns these handles after a failed launch. Their pipe
138
+ // objects release unused ends; stdin still follows the input queue.
139
+ _task = nil;
140
+ dispatch_group_leave(_lifetime);
141
+ return;
142
+ }
143
+ [_state lock];
144
+ _processIdentifier = _task.processIdentifier;
145
+ [_state unlock];
146
+ [_delegate coreProcess:self didLaunch:_processIdentifier];
147
+
148
+ // Exactly two stream readers and one exit observer per child. Each
149
+ // reader invokes its consumer before reading again, applying stream
150
+ // backpressure without a second unbounded callback queue.
151
+ dispatch_group_async(_lifetime, dispatch_get_global_queue(QOS_CLASS_UTILITY, 0), ^{
152
+ [self readOutput:outputPipe.fileHandleForReading stream:ArcaneCoreStandardOutput];
153
+ });
154
+ dispatch_group_async(_lifetime, dispatch_get_global_queue(QOS_CLASS_UTILITY, 0), ^{
155
+ [self readOutput:errorPipe.fileHandleForReading stream:ArcaneCoreStandardError];
156
+ });
157
+ dispatch_group_async(_lifetime, dispatch_get_global_queue(QOS_CLASS_UTILITY, 0), ^{
158
+ [self observeExit];
159
+ });
160
+ dispatch_group_leave(_lifetime);
161
+ }
162
+ }
163
+
164
+ - (BOOL)sendJSONData:(NSData *)data
165
+ completion:(void (^)(NSError * _Nullable error))completion
166
+ error:(NSError * _Nullable * _Nullable)error {
167
+ if (!data || !completion) {
168
+ if (error) *error = ArcaneProcessError(@"CORE_PROCESS_SEND_INVALID",
169
+ @"Supply complete JSON data and a write completion callback.", @"write", nil);
170
+ return NO;
171
+ }
172
+ NSData *body = [data copy];
173
+ [_state lock];
174
+ if (!_accepting) {
175
+ [_state unlock];
176
+ if (error) *error = ArcaneProcessError(@"CORE_PROCESS_INPUT_CLOSED",
177
+ @"Core input no longer accepts requests.", @"write", nil);
178
+ return NO;
179
+ }
180
+ dispatch_group_enter(_lifetime);
181
+ // Enqueue under the same lock as closeInput: every accepted frame precedes
182
+ // the close, even when callers submit and close from different threads.
183
+ dispatch_async(_inputQueue, ^{
184
+ @autoreleasepool {
185
+ NSError *writeError = self->_inputFailure;
186
+ if (!writeError) {
187
+ NSString *header = [NSString stringWithFormat:@"Content-Length: %lu\r\n\r\n",
188
+ (unsigned long)body.length];
189
+ @try {
190
+ if (![self->_input writeData:[header dataUsingEncoding:NSASCIIStringEncoding] error:&writeError]
191
+ || ![self->_input writeData:body error:&writeError]) {
192
+ writeError = ArcaneProcessError(@"CORE_PROCESS_WRITE_FAILED",
193
+ @"The complete Core frame could not be written.", @"write", writeError);
194
+ }
195
+ } @catch (NSException *exception) {
196
+ writeError = ArcaneProcessException(exception, @"write");
197
+ }
198
+ if (writeError) {
199
+ self->_inputFailure = writeError;
200
+ [self reportError:writeError];
201
+ [self closeInput];
202
+ }
203
+ }
204
+ completion(writeError);
205
+ dispatch_group_leave(self->_lifetime);
206
+ }
207
+ });
208
+ [_state unlock];
209
+ return YES;
210
+ }
211
+
212
+ - (void)closeInput {
213
+ [_state lock];
214
+ if (_closeQueued) { [_state unlock]; return; }
215
+ _accepting = NO;
216
+ _closeQueued = YES;
217
+ dispatch_async(_inputQueue, ^{
218
+ [self closeHandle:self->_input operation:@"stdin.close"];
219
+ self->_input = nil;
220
+ dispatch_group_leave(self->_lifetime);
221
+ });
222
+ [_state unlock];
223
+ }
224
+
225
+ - (void)closeHandle:(NSFileHandle *)handle operation:(NSString *)operation {
226
+ if (!handle) return;
227
+ NSError *error = nil;
228
+ @try {
229
+ if (![handle closeAndReturnError:&error]) {
230
+ error = ArcaneProcessError(@"CORE_PROCESS_CLOSE_FAILED",
231
+ @"A Core pipe could not be closed.", operation, error);
232
+ }
233
+ }
234
+ @catch (NSException *exception) { error = ArcaneProcessException(exception, operation); }
235
+ if (error) [self reportError:error];
236
+ }
237
+
238
+ - (BOOL)readContentLength:(NSData *)header result:(NSUInteger *)result {
239
+ NSString *text = [[NSString alloc] initWithData:header encoding:NSASCIIStringEncoding];
240
+ if (!text) return NO;
241
+ BOOL found = NO;
242
+ for (NSString *line in [text componentsSeparatedByString:@"\r\n"]) {
243
+ NSRange colon = [line rangeOfString:@":"];
244
+ if (colon.location == NSNotFound) continue;
245
+ if ([[line substringToIndex:colon.location] caseInsensitiveCompare:@"Content-Length"] != NSOrderedSame) continue;
246
+ if (found) return NO;
247
+ NSString *value = [[line substringFromIndex:colon.location + 1]
248
+ stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]];
249
+ if (!value.length) return NO;
250
+ NSUInteger length = 0;
251
+ for (NSUInteger index = 0; index < value.length; index++) {
252
+ unichar digit = [value characterAtIndex:index];
253
+ if (digit < '0' || digit > '9') return NO;
254
+ NSUInteger next = (NSUInteger)(digit - '0');
255
+ // This is representability of the unavoidable framing field.
256
+ if (length > (NSUIntegerMax - next) / 10) return NO;
257
+ length = length * 10 + next;
258
+ }
259
+ *result = length;
260
+ found = YES;
261
+ }
262
+ return found;
263
+ }
264
+
265
+ - (void)readOutput:(NSFileHandle *)handle stream:(ArcaneCoreOutputStream)stream {
266
+ @autoreleasepool {
267
+ NSMutableData *pending = [NSMutableData data];
268
+ NSData *separator = [@"\r\n\r\n" dataUsingEncoding:NSASCIIStringEncoding];
269
+ BOOL readingBody = NO;
270
+ BOOL decoding = YES;
271
+ NSUInteger expected = 0;
272
+ BOOL reachedEOF = NO;
273
+ while (!reachedEOF) {
274
+ @autoreleasepool {
275
+ NSError *readError = nil;
276
+ NSData *chunk = nil;
277
+ @try { chunk = [handle readDataUpToLength:65536 error:&readError]; }
278
+ @catch (NSException *exception) { readError = ArcaneProcessException(exception, @"read"); }
279
+ if (!chunk) {
280
+ [self reportError:ArcaneProcessError(@"CORE_PROCESS_READ_FAILED",
281
+ @"A Core output stream could not be read.",
282
+ stream == ArcaneCoreStandardOutput ? @"stdout.read" : @"stderr.read", readError)];
283
+ [self closeInput];
284
+ break;
285
+ }
286
+ if (!chunk.length) { reachedEOF = YES; break; }
287
+ [_delegate coreProcess:self receivedOutputData:chunk stream:stream];
288
+ if (stream != ArcaneCoreStandardOutput || !decoding) continue;
289
+ [pending appendData:chunk];
290
+ for (;;) {
291
+ if (!readingBody) {
292
+ NSRange marker = [pending rangeOfData:separator options:0
293
+ range:NSMakeRange(0, pending.length)];
294
+ if (marker.location == NSNotFound) break;
295
+ NSData *header = [pending subdataWithRange:NSMakeRange(0, marker.location)];
296
+ if (![self readContentLength:header result:&expected]) {
297
+ decoding = NO;
298
+ [self reportError:ArcaneProcessError(@"IPC_LENGTH_INVALID",
299
+ @"Core stdout contains an unreadable Content-Length frame header.", @"stdout.frame", nil)];
300
+ [self closeInput];
301
+ break;
302
+ }
303
+ [pending replaceBytesInRange:NSMakeRange(0, NSMaxRange(marker)) withBytes:NULL length:0];
304
+ readingBody = YES;
305
+ }
306
+ if (pending.length < expected) break;
307
+ NSData *frame = [pending subdataWithRange:NSMakeRange(0, expected)];
308
+ [pending replaceBytesInRange:NSMakeRange(0, expected) withBytes:NULL length:0];
309
+ readingBody = NO;
310
+ [_delegate coreProcess:self receivedFrameData:frame];
311
+ }
312
+ }
313
+ }
314
+ if (stream == ArcaneCoreStandardOutput && reachedEOF && decoding && (readingBody || pending.length)) {
315
+ [self reportError:ArcaneProcessError(@"IPC_FRAME_INCOMPLETE",
316
+ @"Core stdout ended during a frame.", @"stdout.frame", nil)];
317
+ [self closeInput];
318
+ }
319
+ [self closeHandle:handle operation:stream == ArcaneCoreStandardOutput ? @"stdout.close" : @"stderr.close"];
320
+ }
321
+ }
322
+
323
+ - (void)observeExit {
324
+ @autoreleasepool {
325
+ // Only this background owner waits. Output readers remain independent.
326
+ int status;
327
+ NSTaskTerminationReason reason;
328
+ @try {
329
+ [_task waitUntilExit];
330
+ status = _task.terminationStatus;
331
+ reason = _task.terminationReason;
332
+ } @catch (NSException *exception) {
333
+ [self reportError:ArcaneProcessException(exception, @"exit.observe")];
334
+ [self closeInput];
335
+ return;
336
+ }
337
+ [self closeInput];
338
+ if (status != 0 || reason != NSTaskTerminationReasonExit) {
339
+ [self reportError:[NSError errorWithDomain:@"ArcaneCoreProcess" code:status userInfo:@{
340
+ NSLocalizedDescriptionKey: @"Core exited without a successful normal completion.",
341
+ @"code": @"CORE_PROCESS_EXIT_FAILED", @"operation": @"exit",
342
+ @"terminationStatus": @(status), @"terminationReason": @(reason)
343
+ }]];
344
+ }
345
+ [_delegate coreProcess:self didExitWithStatus:status reason:reason];
346
+ }
347
+ }
348
+
349
+ - (void)complete {
350
+ [_state lock];
351
+ NSArray<NSError *> *errors = [_errors copy];
352
+ [_state unlock];
353
+ [_delegate coreProcess:self didCompleteWithErrors:errors];
354
+ _delegate = nil;
355
+ _task = nil;
356
+ }
357
+ @end
@@ -0,0 +1,86 @@
1
+ #import <AppKit/AppKit.h>
2
+ #import <WebKit/WebKit.h>
3
+ #import "ArcaneCoreProcess.h"
4
+
5
+ NS_ASSUME_NONNULL_BEGIN
6
+
7
+ @class ArcaneHost;
8
+
9
+ /**
10
+ * Main-thread callbacks. Return promptly; process readers never wait for these
11
+ * callbacks. Output and frame callbacks expose complete native diagnostics,
12
+ * including responses whose original document activation has retired.
13
+ * The delegate is retained until the host finishes closing.
14
+ * Bridge failures preserve NSError domain/code and full nested userInfo;
15
+ * NSURL and NSData use explicit reversible transport records. If an actual
16
+ * native value cannot cross WebKit, the bridge reports that representation
17
+ * failure and the delegate retains the complete original diagnostic.
18
+ */
19
+ @protocol ArcaneHostDelegate <NSObject>
20
+ - (void)host:(ArcaneHost *)host didLaunchCoreProcess:(pid_t)processIdentifier;
21
+ - (void)host:(ArcaneHost *)host receivedOutputData:(NSData *)data stream:(ArcaneCoreOutputStream)stream;
22
+ - (void)host:(ArcaneHost *)host receivedFrameData:(NSData *)data;
23
+ - (void)host:(ArcaneHost *)host didEncounterError:(NSError *)error;
24
+ - (void)host:(ArcaneHost *)host didExitCoreProcessWithStatus:(int)status reason:(NSTaskTerminationReason)reason;
25
+ - (void)host:(ArcaneHost *)host coreDidCompleteWithErrors:(NSArray<NSError *> *)errors;
26
+ @end
27
+
28
+ /**
29
+ * Generic AppKit/WebKit composition. Compile with Objective-C ARC and blocks,
30
+ * linking only AppKit, WebKit and Foundation plus ArcaneCoreProcess.
31
+ * Public methods and properties belong to the main thread.
32
+ */
33
+ API_AVAILABLE(macos(11.0))
34
+ @interface ArcaneHost : NSWindowController
35
+
36
+ /**
37
+ * Opens immediately while the explicit Core executable starts on its own queue.
38
+ * Supply an already-serving applicationURL to load immediately, or nil and call
39
+ * loadApplicationURL: when the serving owner's listener-ready result arrives.
40
+ * Process creation is not listener readiness. The caller owns NSApplication,
41
+ * its menus/delegate, actual serving origin, selected start URL and website
42
+ * data store. Navigation keeps Core and accepted service-lifetime work alive.
43
+ *
44
+ * Supply canonical createCoreClassicSource({eventOwnerModuleURL,
45
+ * replayRuntimeState:true,webKitDocumentLifecycle:true}) output. It is installed
46
+ * unchanged at document start in the main frame's page world, before app code.
47
+ * No file/custom-scheme serving, application client copy or certificate policy
48
+ * is provided here. Arguments exclude argv[0]; Core inherits the environment.
49
+ * Events broadcast to the current document; periods without one remain fully
50
+ * observable through native diagnostics. The shared client replays current
51
+ * runtime state after activation instead of replaying old transient events.
52
+ */
53
+ + (instancetype)openApplicationURL:(NSURL * _Nullable)applicationURL
54
+ websiteDataStore:(WKWebsiteDataStore *)websiteDataStore
55
+ title:(NSString *)title
56
+ contentFrame:(NSRect)contentFrame
57
+ classicSource:(NSString *)classicSource
58
+ executableURL:(NSURL *)executableURL
59
+ arguments:(NSArray<NSString *> *)arguments
60
+ workingDirectoryURL:(NSURL *)workingDirectoryURL
61
+ delegate:(id<ArcaneHostDelegate>)delegate;
62
+
63
+ @property(nonatomic, readonly) WKWebView *webView;
64
+
65
+ /** True after a host/window close requests the existing Core drain. */
66
+ @property(nonatomic, readonly, getter=isClosing) BOOL closing;
67
+
68
+ /** Loads the selected app URL after its serving owner is ready. */
69
+ - (WKNavigation * _Nullable)loadApplicationURL:(NSURL *)applicationURL;
70
+
71
+ /**
72
+ * Idempotent, nonblocking shutdown: stop accepting writes, send ordered stdin
73
+ * EOF, drain process output/exit and pending native deliveries, then close the
74
+ * window. Direct NSWindow.close is also observed and drains while hidden.
75
+ * Each supplied completion runs once on the main thread with all host errors.
76
+ * The caller can use this completion with NSApplicationTerminateLater and its
77
+ * own replyToApplicationShouldTerminate:, without replacing its app delegate.
78
+ */
79
+ - (void)closeWithCompletion:(void (^ _Nullable)(NSArray<NSError *> *errors))completion;
80
+ - (void)close;
81
+
82
+ - (instancetype)init NS_UNAVAILABLE;
83
+ + (instancetype)new NS_UNAVAILABLE;
84
+ @end
85
+
86
+ NS_ASSUME_NONNULL_END