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
package/src/core/host.mjs CHANGED
@@ -1,6 +1,29 @@
1
+ import {readFile} from 'node:fs/promises';
2
+ import Is from 'strong-type';
1
3
  import {createCoreRuntime} from './runtime.mjs';
2
4
  import {startCoreStdio} from './stdio.mjs';
3
5
 
6
+ const is = new Is(false);
7
+
8
+ /**
9
+ * Read only the explicitly supplied --arcane-launch-config filename.
10
+ * Return the complete JSON object for the service factories' second argument;
11
+ * field meanings belong to the launch owner and individual services.
12
+ */
13
+ export async function readCoreLaunchContext({argv = process.argv.slice(2)} = {}) {
14
+ const flag = argv.indexOf('--arcane-launch-config');
15
+ if (flag === -1) return {};
16
+ const filename = argv[flag + 1];
17
+ if (filename === undefined || filename === '') {
18
+ throw new TypeError('--arcane-launch-config requires a filename.');
19
+ }
20
+ const context = JSON.parse(await readFile(filename, 'utf8'));
21
+ if (context === null || !is.object(context) || is.array(context)) {
22
+ throw new TypeError('Core launch configuration must be a JSON object.');
23
+ }
24
+ return context;
25
+ }
26
+
4
27
  /** Compose app services with the shared dispatcher and draining transport. */
5
28
  export function startCoreHost({application, version, services = [], input, output, onError, signal} = {}) {
6
29
  const runtime = createCoreRuntime({application, version, services});
@@ -0,0 +1,85 @@
1
+ #import <Foundation/Foundation.h>
2
+
3
+ NS_ASSUME_NONNULL_BEGIN
4
+
5
+ @class ArcaneCoreProcess;
6
+
7
+ typedef NS_ENUM(NSUInteger, ArcaneCoreOutputStream) {
8
+ ArcaneCoreStandardOutput,
9
+ ArcaneCoreStandardError
10
+ };
11
+
12
+ /**
13
+ * Callbacks run on process-owned background queues. Each output stream retains
14
+ * its order; different streams, writes and exit observation can be concurrent.
15
+ * Marshal UI work at the host. Callbacks must return normally and must not wait
16
+ * for another callback or for completion. The process retains its delegate
17
+ * through didCompleteWithErrors, then releases it.
18
+ *
19
+ * receivedOutputData delivers every unmodified stdout/stderr chunk. Stdout
20
+ * includes framing; receivedFrameData additionally delivers each complete
21
+ * opaque JSON body. No payload is decoded, parsed or rewritten here. Following
22
+ * a framing error, raw stdout continues to EOF without guessing a new boundary.
23
+ */
24
+ @protocol ArcaneCoreProcessDelegate <NSObject>
25
+ - (void)coreProcess:(ArcaneCoreProcess *)process didLaunch:(pid_t)processIdentifier;
26
+ - (void)coreProcess:(ArcaneCoreProcess *)process receivedFrameData:(NSData *)data;
27
+ - (void)coreProcess:(ArcaneCoreProcess *)process receivedOutputData:(NSData *)data
28
+ stream:(ArcaneCoreOutputStream)stream;
29
+ - (void)coreProcess:(ArcaneCoreProcess *)process didEncounterError:(NSError *)error;
30
+ - (void)coreProcess:(ArcaneCoreProcess *)process didExitWithStatus:(int)status
31
+ reason:(NSTaskTerminationReason)reason;
32
+ /**
33
+ * Last callback: launch/exit observation, accepted writes and both streams have
34
+ * settled, successfully or with reported errors. Launch failure has no exit
35
+ * callback; an exit-observation failure is not proof that the child exited.
36
+ */
37
+ - (void)coreProcess:(ArcaneCoreProcess *)process didCompleteWithErrors:(NSArray<NSError *> *)errors;
38
+ @end
39
+
40
+ /**
41
+ * Foundation-only ownership of one explicit Core executable and framed pipes.
42
+ * Compile with Objective-C ARC and blocks; no third-party runtime is linked.
43
+ */
44
+ API_AVAILABLE(macos(11.0))
45
+ @interface ArcaneCoreProcess : NSObject
46
+
47
+ /**
48
+ * Starts asynchronously without shell execution, PATH lookup, environment
49
+ * mutation or a foreground wait. Arguments exclude argv[0]; the selected child
50
+ * inherits the host environment. Launch failure is reported through the
51
+ * delegate and completion. Callbacks can begin before this method returns.
52
+ */
53
+ + (instancetype)startExecutableURL:(NSURL *)executableURL
54
+ arguments:(NSArray<NSString *> *)arguments
55
+ workingDirectoryURL:(NSURL *)workingDirectoryURL
56
+ delegate:(id<ArcaneCoreProcessDelegate>)delegate;
57
+
58
+ /** Zero until a successful launch; the original child PID remains after exit. */
59
+ @property(nonatomic, readonly) pid_t processIdentifier;
60
+
61
+ /**
62
+ * Copies and queues the complete opaque JSON body. YES accepts the write and
63
+ * calls completion exactly once on the input queue. nil completion-error means
64
+ * the full frame reached child stdin, not that Core accepted or completed its
65
+ * operation. NO supplies a synchronous error and never calls completion.
66
+ * A partial/failed write is never retried. Payload length is transport framing
67
+ * only, never an application limit or policy.
68
+ */
69
+ - (BOOL)sendJSONData:(NSData *)data
70
+ completion:(void (^)(NSError * _Nullable error))completion
71
+ error:(NSError * _Nullable * _Nullable)error;
72
+
73
+ /**
74
+ * Idempotent and nonblocking: reject new sends, finish accepted writes, then
75
+ * close stdin. Keep reading stdout/stderr and observe child exit. There is no
76
+ * forced termination or deadline. Accepted service work can outlive a renderer;
77
+ * renderer cancellation belongs to the public Core control protocol.
78
+ */
79
+ - (void)closeInput;
80
+
81
+ - (instancetype)init NS_UNAVAILABLE;
82
+ + (instancetype)new NS_UNAVAILABLE;
83
+ @end
84
+
85
+ NS_ASSUME_NONNULL_END
@@ -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
@@ -94,8 +94,8 @@ namespace Arcane.Core.Hosts.Windows
94
94
  throw failure;
95
95
  }
96
96
  // All callbacks and exit observation exist before either reader starts.
97
- host.outputReader = Task.Run(host.ReadOutputAsync);
98
- host.diagnosticReader = Task.Run(host.ReadDiagnosticsAsync);
97
+ host.outputReader = Task.Run(new Func<Task>(host.ReadOutputAsync));
98
+ host.diagnosticReader = Task.Run(new Func<Task>(host.ReadDiagnosticsAsync));
99
99
  host.lifetime = host.CompleteAsync();
100
100
  host.lifetime.ContinueWith(host.ObserveLifetimeFailure, CancellationToken.None,
101
101
  TaskContinuationOptions.OnlyOnFaulted, TaskScheduler.Default);
@@ -164,7 +164,7 @@ namespace Arcane.Core.Hosts.Windows
164
164
  {
165
165
  if (inputClosing != null) return inputClosing;
166
166
  accepting = false;
167
- inputClosing = writes.ContinueWith(
167
+ inputClosing = writes.ContinueWith<Task>(
168
168
  CloseInputAfterWritesAsync, CancellationToken.None,
169
169
  TaskContinuationOptions.None, TaskScheduler.Default).Unwrap();
170
170
  return inputClosing;