arcane-os 0.88.1 → 0.90.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 +34 -0
- package/browser-runtime/ai/browser-decisions.mjs +31 -4
- package/browser-runtime/ai/core-local.mjs +105 -25
- package/browser-runtime/ai/decision-runtime.mjs +7 -0
- package/browser-runtime/ai/decision-worker.mjs +9 -1
- package/browser-runtime/core/client.mjs +6 -0
- package/browser-runtime/core/contracts.mjs +10 -0
- package/docs/reference/ai/browser-decisions.md +89 -7
- package/docs/reference/core-client.md +59 -0
- package/docs/reference/core-native-packaging.md +62 -10
- package/docs/reference/core-repositories.md +104 -8
- package/docs/reference/git-text-snapshot.md +16 -1
- package/docs/reference/inventory/package-api.json +25 -25
- package/docs/reference/inventory/runtime-components.json +1 -1
- package/docs/reference/inventory/runtime-modules.json +4 -4
- package/docs/reference/local-ai.md +108 -9
- package/docs/reference/mcp-stdio.md +76 -1
- package/docs/reference/native-app-control.md +17 -5
- package/docs/reference/protocols.md +14 -0
- package/docs/reference/runtime-modules.md +9 -3
- package/docs/reference/sdk-api.md +202 -35
- package/package.json +2 -2
- package/runtime/arcane/modules/RiskSignalAnalyzer.js +1 -5
- package/schemas/arcane-app.schema.json +13 -3
- package/schemas/arcane-package.schema.json +10 -1
- package/src/app-descriptor.mjs +6 -2
- package/src/cli/app-control.mjs +1 -1
- package/src/core/hosts/windows/ArcaneHost.cs +273 -5
- package/src/core/hosts/windows/ArcaneLauncher.cs +9 -0
- package/src/core/hosts/windows/ArcaneWindowControl.cs +7 -7
- package/src/core/repositories.mjs +17 -6
- package/src/core/services/local-ai.mjs +183 -22
- package/src/git-text-snapshot.mjs +4 -2
- package/src/installed-sdk-runtime.mjs +2 -0
- package/src/local-ai/config.mjs +16 -0
- package/src/local-ai/dev.mjs +2 -2
- package/src/local-ai/discover.mjs +3 -0
- package/src/local-ai/install.mjs +130 -20
- package/src/local-ai/llama-execution-windows.ps1 +158 -0
- package/src/local-ai/llama-execution.mjs +169 -0
- package/src/local-ai/server.mjs +38 -8
- package/src/native/portable-layout.mjs +13 -3
- package/src/repository.mjs +33 -8
- package/src/workspace-runtime.mjs +97 -1
|
@@ -33,6 +33,7 @@ namespace Arcane.Core.Hosts.Windows
|
|
|
33
33
|
public double? InitialClientWidth { get; set; }
|
|
34
34
|
public double? InitialClientHeight { get; set; }
|
|
35
35
|
public bool? Resizable { get; set; }
|
|
36
|
+
public string InitialWindowState { get; set; }
|
|
36
37
|
}
|
|
37
38
|
|
|
38
39
|
public static class ArcaneHost
|
|
@@ -130,6 +131,7 @@ namespace Arcane.Core.Hosts.Windows
|
|
|
130
131
|
private bool browserProcessExited;
|
|
131
132
|
private bool closing;
|
|
132
133
|
private bool closeAllowed;
|
|
134
|
+
private WindowRestoreSnapshot fullscreenRestore;
|
|
133
135
|
|
|
134
136
|
public ArcaneHostForm(ArcaneHostOptions options, Action<string> onDiagnostic,
|
|
135
137
|
Action<Exception> onError = null)
|
|
@@ -138,6 +140,7 @@ namespace Arcane.Core.Hosts.Windows
|
|
|
138
140
|
if (onDiagnostic == null) throw new ArgumentNullException("onDiagnostic");
|
|
139
141
|
ValidateInitialDimension(options.InitialClientWidth, "InitialClientWidth");
|
|
140
142
|
ValidateInitialDimension(options.InitialClientHeight, "InitialClientHeight");
|
|
143
|
+
if (options.InitialWindowState != null) ValidateWindowState(options.InitialWindowState);
|
|
141
144
|
this.options = options;
|
|
142
145
|
this.onDiagnostic = onDiagnostic;
|
|
143
146
|
this.onError = onError;
|
|
@@ -160,9 +163,13 @@ namespace Arcane.Core.Hosts.Windows
|
|
|
160
163
|
|
|
161
164
|
protected override void OnLoad(EventArgs args)
|
|
162
165
|
{
|
|
163
|
-
// Apply the initial
|
|
164
|
-
//
|
|
165
|
-
if (!started && !closing)
|
|
166
|
+
// Apply the initial choices before the composing launcher's Load
|
|
167
|
+
// handler. Later app/user sizing remains application-owned.
|
|
168
|
+
if (!started && !closing)
|
|
169
|
+
{
|
|
170
|
+
ApplyInitialWindowSize();
|
|
171
|
+
if (options.InitialWindowState != null) ApplyWindowState(options.InitialWindowState);
|
|
172
|
+
}
|
|
166
173
|
base.OnLoad(args);
|
|
167
174
|
if (started || closing) return;
|
|
168
175
|
started = true;
|
|
@@ -202,6 +209,263 @@ namespace Arcane.Core.Hosts.Windows
|
|
|
202
209
|
Math.Max(workArea.Top, Math.Min(Top, workArea.Bottom - Height)));
|
|
203
210
|
}
|
|
204
211
|
|
|
212
|
+
private static void ValidateWindowState(string state)
|
|
213
|
+
{
|
|
214
|
+
if (state != "normal" && state != "maximized" && state != "fullscreen")
|
|
215
|
+
throw new ArgumentException("Select window state normal, maximized, or fullscreen.");
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
private FormWindowState CurrentWindowState()
|
|
219
|
+
{
|
|
220
|
+
if (!IsHandleCreated) return WindowState;
|
|
221
|
+
if (IsIconic(Handle)) return FormWindowState.Minimized;
|
|
222
|
+
return IsZoomed(Handle) ? FormWindowState.Maximized : FormWindowState.Normal;
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
private string WindowStateName()
|
|
226
|
+
{
|
|
227
|
+
FormWindowState state = CurrentWindowState();
|
|
228
|
+
if (state != FormWindowState.Normal) return state.ToString();
|
|
229
|
+
return fullscreenRestore != null && FormBorderStyle == FormBorderStyle.None
|
|
230
|
+
? "Fullscreen" : "Normal";
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
private Dictionary<string, object> WindowStateRecord()
|
|
234
|
+
{
|
|
235
|
+
return new Dictionary<string, object>
|
|
236
|
+
{
|
|
237
|
+
{ "platform", "windows" }, { "supported", true },
|
|
238
|
+
{ "state", WindowStateName().ToLowerInvariant() }
|
|
239
|
+
};
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
private void ApplyWindowState(string state)
|
|
243
|
+
{
|
|
244
|
+
ValidateWindowState(state);
|
|
245
|
+
bool visible = IsHandleCreated && IsWindowVisible(Handle);
|
|
246
|
+
if (visible && state == WindowStateName().ToLowerInvariant()
|
|
247
|
+
&& (state == "fullscreen" || fullscreenRestore == null)) return;
|
|
248
|
+
// Initial hidden configuration keeps ordinary startup behavior. A
|
|
249
|
+
// visible WinForms state setter can activate through ShowWindow.
|
|
250
|
+
WindowStateTransition transition = visible ? new WindowStateTransition(this) : null;
|
|
251
|
+
Exception mutationError = null;
|
|
252
|
+
try { ChangeWindowState(state); }
|
|
253
|
+
catch (Exception error)
|
|
254
|
+
{
|
|
255
|
+
mutationError = error;
|
|
256
|
+
throw;
|
|
257
|
+
}
|
|
258
|
+
finally
|
|
259
|
+
{
|
|
260
|
+
Exception cleanupError = transition == null ? null : transition.Release();
|
|
261
|
+
if (cleanupError != null)
|
|
262
|
+
{
|
|
263
|
+
if (mutationError != null)
|
|
264
|
+
throw new AggregateException("Window state change and activation-hook cleanup failed.",
|
|
265
|
+
mutationError, cleanupError);
|
|
266
|
+
throw cleanupError;
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
private void ChangeWindowState(string state)
|
|
272
|
+
{
|
|
273
|
+
if (state == "fullscreen")
|
|
274
|
+
{
|
|
275
|
+
Rectangle screen = Screen.FromControl(this).Bounds;
|
|
276
|
+
if (fullscreenRestore == null)
|
|
277
|
+
{
|
|
278
|
+
fullscreenRestore = new WindowRestoreSnapshot(
|
|
279
|
+
CurrentWindowState() == FormWindowState.Normal ? Bounds : RestoreBounds,
|
|
280
|
+
FormBorderStyle, MinimizeBox, MaximizeBox, ControlBox);
|
|
281
|
+
}
|
|
282
|
+
// Retain the normal frame and bounds before removing chrome.
|
|
283
|
+
// This affects this window only: no activation or TopMost.
|
|
284
|
+
WindowState = FormWindowState.Normal;
|
|
285
|
+
FormBorderStyle = FormBorderStyle.None;
|
|
286
|
+
Bounds = screen;
|
|
287
|
+
return;
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
if (fullscreenRestore != null)
|
|
291
|
+
{
|
|
292
|
+
// Restore normal geometry before maximizing so WinForms keeps
|
|
293
|
+
// the same restore bounds for a later Normal selection.
|
|
294
|
+
WindowState = FormWindowState.Normal;
|
|
295
|
+
FormBorderStyle = fullscreenRestore.BorderStyle;
|
|
296
|
+
MinimizeBox = fullscreenRestore.MinimizeBox;
|
|
297
|
+
MaximizeBox = fullscreenRestore.MaximizeBox;
|
|
298
|
+
ControlBox = fullscreenRestore.ControlBox;
|
|
299
|
+
Bounds = fullscreenRestore.Bounds;
|
|
300
|
+
fullscreenRestore = null;
|
|
301
|
+
}
|
|
302
|
+
WindowState = state == "maximized" ? FormWindowState.Maximized : FormWindowState.Normal;
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
private string WindowStateResponse(Dictionary<string, object> request, string id, bool apply)
|
|
306
|
+
{
|
|
307
|
+
Dictionary<string, object> previous = WindowStateRecord();
|
|
308
|
+
object parameters;
|
|
309
|
+
request.TryGetValue("parameters", out parameters);
|
|
310
|
+
Dictionary<string, object> response = new Dictionary<string, object>
|
|
311
|
+
{
|
|
312
|
+
{ "protocol", "arcane/1" }, { "type", "response" }, { "id", id }
|
|
313
|
+
};
|
|
314
|
+
try
|
|
315
|
+
{
|
|
316
|
+
if (apply)
|
|
317
|
+
{
|
|
318
|
+
Dictionary<string, object> selection = parameters as Dictionary<string, object>;
|
|
319
|
+
object state;
|
|
320
|
+
if (selection == null || !selection.TryGetValue("state", out state) || !(state is string))
|
|
321
|
+
throw new ArgumentException("Supply a window state selection.");
|
|
322
|
+
ApplyWindowState((string)state);
|
|
323
|
+
}
|
|
324
|
+
response["ok"] = true;
|
|
325
|
+
response["result"] = WindowStateRecord();
|
|
326
|
+
}
|
|
327
|
+
catch (Exception error)
|
|
328
|
+
{
|
|
329
|
+
Dictionary<string, object> record = (Dictionary<string, object>)ArcaneHost.ErrorRecord(error,
|
|
330
|
+
error is ArgumentException ? "INVALID_ARGUMENT" : "ARCANE_WINDOW_STATE_FAILED");
|
|
331
|
+
record["details"] = new Dictionary<string, object>
|
|
332
|
+
{
|
|
333
|
+
{ "requested", parameters }, { "previous", previous }, { "actual", WindowStateRecord() }
|
|
334
|
+
};
|
|
335
|
+
response["ok"] = false;
|
|
336
|
+
response["error"] = record;
|
|
337
|
+
}
|
|
338
|
+
return ArcaneHost.Serializer().Serialize(response);
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
private sealed class WindowRestoreSnapshot
|
|
342
|
+
{
|
|
343
|
+
internal readonly Rectangle Bounds;
|
|
344
|
+
internal readonly FormBorderStyle BorderStyle;
|
|
345
|
+
internal readonly bool MinimizeBox;
|
|
346
|
+
internal readonly bool MaximizeBox;
|
|
347
|
+
internal readonly bool ControlBox;
|
|
348
|
+
|
|
349
|
+
internal WindowRestoreSnapshot(Rectangle bounds, FormBorderStyle borderStyle,
|
|
350
|
+
bool minimizeBox, bool maximizeBox, bool controlBox)
|
|
351
|
+
{
|
|
352
|
+
Bounds = bounds;
|
|
353
|
+
BorderStyle = borderStyle;
|
|
354
|
+
MinimizeBox = minimizeBox;
|
|
355
|
+
MaximizeBox = maximizeBox;
|
|
356
|
+
ControlBox = controlBox;
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
private sealed class WindowStateTransition
|
|
361
|
+
{
|
|
362
|
+
private const int WhCbt = 5;
|
|
363
|
+
private const int HcbtActivate = 5;
|
|
364
|
+
private const int HcbtSetFocus = 9;
|
|
365
|
+
private delegate IntPtr HookProcedure(int code, IntPtr target, IntPtr details);
|
|
366
|
+
// A failed native unhook must never retain a collected delegate.
|
|
367
|
+
private static readonly HookProcedure callback = BeforeWindowChange;
|
|
368
|
+
[ThreadStatic] private static WindowStateTransition current;
|
|
369
|
+
[ThreadStatic] private static List<WindowStateTransition> pendingCleanup;
|
|
370
|
+
private readonly ArcaneHostForm owner;
|
|
371
|
+
private readonly WindowStateTransition previous;
|
|
372
|
+
private IntPtr hook;
|
|
373
|
+
private bool active;
|
|
374
|
+
|
|
375
|
+
internal WindowStateTransition(ArcaneHostForm owner)
|
|
376
|
+
{
|
|
377
|
+
this.owner = owner;
|
|
378
|
+
previous = current;
|
|
379
|
+
hook = SetWindowsHookEx(WhCbt, callback, IntPtr.Zero, GetCurrentThreadId());
|
|
380
|
+
if (hook == IntPtr.Zero)
|
|
381
|
+
{
|
|
382
|
+
int code = Marshal.GetLastWin32Error();
|
|
383
|
+
System.ComponentModel.Win32Exception error = new System.ComponentModel.Win32Exception(code);
|
|
384
|
+
error.Data["operation"] = "SetWindowsHookExW";
|
|
385
|
+
error.Data["nativeErrorCode"] = code;
|
|
386
|
+
throw error;
|
|
387
|
+
}
|
|
388
|
+
active = true;
|
|
389
|
+
current = this;
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
private static IntPtr BeforeWindowChange(int code, IntPtr target, IntPtr details)
|
|
393
|
+
{
|
|
394
|
+
if (code == HcbtActivate || code == HcbtSetFocus)
|
|
395
|
+
{
|
|
396
|
+
for (WindowStateTransition transition = current; transition != null; transition = transition.previous)
|
|
397
|
+
{
|
|
398
|
+
if (!transition.active || !transition.owner.IsHandleCreated) continue;
|
|
399
|
+
IntPtr window = transition.owner.Handle;
|
|
400
|
+
if (target == window || (code == HcbtSetFocus && IsChild(window, target)))
|
|
401
|
+
return new IntPtr(1);
|
|
402
|
+
}
|
|
403
|
+
}
|
|
404
|
+
return CallNextHookEx(IntPtr.Zero, code, target, details);
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
internal Exception Release()
|
|
408
|
+
{
|
|
409
|
+
// Stop vetoing immediately, even if the native unhook fails.
|
|
410
|
+
active = false;
|
|
411
|
+
if (current == this) current = previous;
|
|
412
|
+
if (hook == IntPtr.Zero) return null;
|
|
413
|
+
if (UnhookWindowsHookEx(hook))
|
|
414
|
+
{
|
|
415
|
+
hook = IntPtr.Zero;
|
|
416
|
+
if (pendingCleanup != null) pendingCleanup.Remove(this);
|
|
417
|
+
return null;
|
|
418
|
+
}
|
|
419
|
+
int code = Marshal.GetLastWin32Error();
|
|
420
|
+
if (pendingCleanup == null) pendingCleanup = new List<WindowStateTransition>();
|
|
421
|
+
if (!pendingCleanup.Contains(this)) pendingCleanup.Add(this);
|
|
422
|
+
System.ComponentModel.Win32Exception error = new System.ComponentModel.Win32Exception(code);
|
|
423
|
+
error.Data["operation"] = "UnhookWindowsHookEx";
|
|
424
|
+
error.Data["nativeErrorCode"] = code;
|
|
425
|
+
return error;
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
internal static void ReleasePending(ArcaneHostForm owner)
|
|
429
|
+
{
|
|
430
|
+
if (pendingCleanup == null) return;
|
|
431
|
+
foreach (WindowStateTransition transition in pendingCleanup.ToArray())
|
|
432
|
+
{
|
|
433
|
+
if (transition.owner != owner) continue;
|
|
434
|
+
Exception error = transition.Release();
|
|
435
|
+
if (error != null) owner.Report(error);
|
|
436
|
+
}
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
[DllImport("user32.dll", EntryPoint = "SetWindowsHookExW", SetLastError = true)]
|
|
440
|
+
private static extern IntPtr SetWindowsHookEx(int kind, HookProcedure procedure, IntPtr module, uint threadId);
|
|
441
|
+
|
|
442
|
+
[DllImport("user32.dll", SetLastError = true)]
|
|
443
|
+
[return: MarshalAs(UnmanagedType.Bool)]
|
|
444
|
+
private static extern bool UnhookWindowsHookEx(IntPtr hook);
|
|
445
|
+
|
|
446
|
+
[DllImport("user32.dll")]
|
|
447
|
+
private static extern IntPtr CallNextHookEx(IntPtr hook, int code, IntPtr target, IntPtr details);
|
|
448
|
+
|
|
449
|
+
[DllImport("user32.dll")]
|
|
450
|
+
[return: MarshalAs(UnmanagedType.Bool)]
|
|
451
|
+
private static extern bool IsChild(IntPtr parent, IntPtr child);
|
|
452
|
+
|
|
453
|
+
[DllImport("kernel32.dll")]
|
|
454
|
+
private static extern uint GetCurrentThreadId();
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
[DllImport("user32.dll")]
|
|
458
|
+
[return: MarshalAs(UnmanagedType.Bool)]
|
|
459
|
+
private static extern bool IsWindowVisible(IntPtr window);
|
|
460
|
+
|
|
461
|
+
[DllImport("user32.dll")]
|
|
462
|
+
[return: MarshalAs(UnmanagedType.Bool)]
|
|
463
|
+
private static extern bool IsIconic(IntPtr window);
|
|
464
|
+
|
|
465
|
+
[DllImport("user32.dll")]
|
|
466
|
+
[return: MarshalAs(UnmanagedType.Bool)]
|
|
467
|
+
private static extern bool IsZoomed(IntPtr window);
|
|
468
|
+
|
|
205
469
|
private async Task InitializeAsync()
|
|
206
470
|
{
|
|
207
471
|
try
|
|
@@ -296,12 +560,15 @@ namespace Arcane.Core.Hosts.Windows
|
|
|
296
560
|
{
|
|
297
561
|
if (documentGeneration != generation || closing)
|
|
298
562
|
throw new InvalidOperationException("The requesting document is no longer connected.");
|
|
563
|
+
string method = EnvelopeString(envelope, "method");
|
|
299
564
|
if (requestId != null && EnvelopeString(envelope, "protocol") == "arcane/1"
|
|
300
|
-
&&
|
|
565
|
+
&& (method == "window.setTheme" || method == "window.state" || method == "window.setState"))
|
|
301
566
|
{
|
|
302
567
|
// This method belongs to this exact window, not the Core
|
|
303
568
|
// child or a machine-wide appearance service.
|
|
304
|
-
string response =
|
|
569
|
+
string response = method == "window.setTheme"
|
|
570
|
+
? WindowThemeResponse(envelope, requestId)
|
|
571
|
+
: WindowStateResponse(envelope, requestId, method == "window.setState");
|
|
305
572
|
pending.Enqueue(
|
|
306
573
|
new CoreDelivery(documentGeneration, response)
|
|
307
574
|
);
|
|
@@ -747,6 +1014,7 @@ namespace Arcane.Core.Hosts.Windows
|
|
|
747
1014
|
if (bridge != null) bridge.Stop(new InvalidOperationException("The application window is closing."));
|
|
748
1015
|
if (activeBridge != null) activeBridge.Stop(new InvalidOperationException("The application window is closing."));
|
|
749
1016
|
StopAppControl();
|
|
1017
|
+
WindowStateTransition.ReleasePending(this);
|
|
750
1018
|
shutdown = DrainAndCloseAsync();
|
|
751
1019
|
shutdown.ContinueWith(ObserveUnexpectedFailure, TaskContinuationOptions.OnlyOnFaulted);
|
|
752
1020
|
}
|
|
@@ -98,6 +98,7 @@ namespace Arcane.Core.Hosts.Windows
|
|
|
98
98
|
InitialClientWidth = OptionalWindowDimension(windowOptions, "width"),
|
|
99
99
|
InitialClientHeight = OptionalWindowDimension(windowOptions, "height"),
|
|
100
100
|
Resizable = OptionalWindowResizable(windowOptions),
|
|
101
|
+
InitialWindowState = OptionalWindowState(windowOptions),
|
|
101
102
|
IconPath = String.IsNullOrEmpty(icon) ? null : Path.Combine(directory, icon),
|
|
102
103
|
ClassicClientSource = File.ReadAllText(Path.Combine(directory, RequiredString(client, "source"))),
|
|
103
104
|
AppControlEndpoint = appControl == null ? null : RequiredString(appControl, "endpoint"),
|
|
@@ -210,6 +211,14 @@ namespace Arcane.Core.Hosts.Windows
|
|
|
210
211
|
throw new FormatException("The native manifest needs a boolean at window.resizable.");
|
|
211
212
|
}
|
|
212
213
|
|
|
214
|
+
private static string OptionalWindowState(Dictionary<string, object> window)
|
|
215
|
+
{
|
|
216
|
+
object value;
|
|
217
|
+
if (window == null || !window.TryGetValue("state", out value)) return null;
|
|
218
|
+
if (value is string) return (string)value;
|
|
219
|
+
throw new FormatException("The native manifest needs a string at window.state.");
|
|
220
|
+
}
|
|
221
|
+
|
|
213
222
|
private static string QuoteArgument(string value)
|
|
214
223
|
{
|
|
215
224
|
// Windows command-line escaping is transport-local. The child
|
|
@@ -148,7 +148,7 @@ namespace Arcane.Core.Hosts.Windows
|
|
|
148
148
|
{
|
|
149
149
|
return new Dictionary<string, object>
|
|
150
150
|
{
|
|
151
|
-
{ "title", Text }, { "state",
|
|
151
|
+
{ "title", Text }, { "state", WindowStateName() },
|
|
152
152
|
{ "width", ClientSize.Width }, { "height", ClientSize.Height }
|
|
153
153
|
};
|
|
154
154
|
}
|
|
@@ -252,8 +252,8 @@ namespace Arcane.Core.Hosts.Windows
|
|
|
252
252
|
{
|
|
253
253
|
object value;
|
|
254
254
|
string key = parameters.TryGetValue("key", out value) ? value as string : null;
|
|
255
|
-
if (key != "Tab" && key != "Enter" && key != "Space")
|
|
256
|
-
throw new ArgumentException("key must be Tab, Enter, or
|
|
255
|
+
if (key != "Tab" && key != "Enter" && key != "Space" && key != "Escape")
|
|
256
|
+
throw new ArgumentException("key must be Tab, Enter, Space, or Escape.");
|
|
257
257
|
bool shiftKey = false;
|
|
258
258
|
if (parameters.TryGetValue("shiftKey", out value))
|
|
259
259
|
{
|
|
@@ -263,14 +263,14 @@ namespace Arcane.Core.Hosts.Windows
|
|
|
263
263
|
if (shiftKey && key != "Tab") throw new ArgumentException("shiftKey is supported only with Tab.");
|
|
264
264
|
Dictionary<string, object> down = new Dictionary<string, object>
|
|
265
265
|
{
|
|
266
|
-
{ "type", key == "Tab" ? "rawKeyDown" : "keyDown" },
|
|
266
|
+
{ "type", key == "Tab" || key == "Escape" ? "rawKeyDown" : "keyDown" },
|
|
267
267
|
{ "key", key == "Space" ? " " : key }, { "code", key },
|
|
268
|
-
{ "windowsVirtualKeyCode", key == "Tab" ? 9 : key == "Enter" ? 13 : 32 },
|
|
268
|
+
{ "windowsVirtualKeyCode", key == "Tab" ? 9 : key == "Enter" ? 13 : key == "Escape" ? 27 : 32 },
|
|
269
269
|
{ "modifiers", shiftKey ? 8 : 0 }
|
|
270
270
|
};
|
|
271
271
|
Dictionary<string, object> up = new Dictionary<string, object>(down);
|
|
272
272
|
up["type"] = "keyUp";
|
|
273
|
-
if (key
|
|
273
|
+
if (key == "Enter" || key == "Space")
|
|
274
274
|
{
|
|
275
275
|
down["text"] = key == "Enter" ? "\r" : " ";
|
|
276
276
|
down["unmodifiedText"] = down["text"];
|
|
@@ -345,7 +345,7 @@ namespace Arcane.Core.Hosts.Windows
|
|
|
345
345
|
|
|
346
346
|
private void RequireAppControlNormalWindow()
|
|
347
347
|
{
|
|
348
|
-
if (
|
|
348
|
+
if (WindowStateName() == "Normal") return;
|
|
349
349
|
InvalidOperationException error = new InvalidOperationException("App-control resize requires a Normal window.");
|
|
350
350
|
error.Data["reason"] = "unsupported-window-state";
|
|
351
351
|
error.Data["supportedWindowState"] = "Normal";
|
|
@@ -4,7 +4,7 @@ import path from 'node:path';
|
|
|
4
4
|
import Is from 'strong-type';
|
|
5
5
|
import {ArcaneError, throwIfAborted} from '../errors.mjs';
|
|
6
6
|
import {runProcess} from '../process.mjs';
|
|
7
|
-
import {captureRepositoryTarget, repositoryConfiguration,
|
|
7
|
+
import {captureRepositoryRemote, captureRepositoryTarget, repositoryConfiguration, pullRepositoryTarget, pushRepositoryTarget,
|
|
8
8
|
repositoryPushArguments, repositoryStatus} from '../repository.mjs';
|
|
9
9
|
import {createGitIdentityRunner} from '../git-identity.mjs';
|
|
10
10
|
|
|
@@ -45,7 +45,7 @@ export function resolveArcaneDataPaths({dataRoot} = {}) {
|
|
|
45
45
|
|
|
46
46
|
/** One connected checkout, composed into an application's own Core service. */
|
|
47
47
|
export function createRepositoryWorkspace(
|
|
48
|
-
{name, directory, dataRoot, remote, branch, longPaths, gitIdentity, onEvent, run = runProcess} = {}
|
|
48
|
+
{name, directory, dataRoot, remote, remoteBase, branch, longPaths, cloneIdentity = {}, gitIdentity, onEvent, run = runProcess} = {}
|
|
49
49
|
) {
|
|
50
50
|
let selected = directory;
|
|
51
51
|
if (selected === undefined) {
|
|
@@ -57,11 +57,21 @@ export function createRepositoryWorkspace(
|
|
|
57
57
|
}
|
|
58
58
|
requireString(selected, 'directory');
|
|
59
59
|
if (remote !== undefined) requireString(remote, 'remote');
|
|
60
|
+
const remoteArgument = captureRepositoryRemote(remote, remoteBase);
|
|
60
61
|
if (branch !== undefined) requireString(branch, 'branch');
|
|
61
62
|
if (longPaths !== undefined && !is.boolean(longPaths)) {
|
|
62
63
|
throw new TypeError('longPaths must be a boolean when supplied.');
|
|
63
64
|
}
|
|
64
65
|
if (!is.function(run)) throw new TypeError('run must implement the SDK process adapter.');
|
|
66
|
+
const {name: cloneName, email: cloneEmail} = cloneIdentity;
|
|
67
|
+
const cloneConfiguration = [];
|
|
68
|
+
for (const [field, value] of Object.entries({name: cloneName, email: cloneEmail})) {
|
|
69
|
+
if (value === undefined) continue;
|
|
70
|
+
if (!is.string(value) || !value.isWellFormed() || value.includes('\0')) {
|
|
71
|
+
throw new TypeError(`cloneIdentity.${field} must be text representable as a native process argument.`);
|
|
72
|
+
}
|
|
73
|
+
cloneConfiguration.push('--config', `user.${field}=${value}`);
|
|
74
|
+
}
|
|
65
75
|
const execute = createGitIdentityRunner(run, gitIdentity);
|
|
66
76
|
const repositoryDirectory = path.resolve(selected);
|
|
67
77
|
const pending = new Set();
|
|
@@ -108,8 +118,9 @@ export function createRepositoryWorkspace(
|
|
|
108
118
|
if (process.platform === 'win32' && longPaths !== undefined) {
|
|
109
119
|
arguments_.push('--config', `core.longpaths=${longPaths}`);
|
|
110
120
|
}
|
|
121
|
+
arguments_.push(...cloneConfiguration);
|
|
111
122
|
if (branch !== undefined) arguments_.push('--branch', branch);
|
|
112
|
-
arguments_.push('--',
|
|
123
|
+
arguments_.push('--', remoteArgument, repositoryDirectory);
|
|
113
124
|
const result = await prepareRun(
|
|
114
125
|
'git',
|
|
115
126
|
arguments_,
|
|
@@ -177,7 +188,7 @@ export function createRepositoryWorkspace(
|
|
|
177
188
|
return accept(
|
|
178
189
|
async function pullRepository() {
|
|
179
190
|
await prepare(signal);
|
|
180
|
-
return
|
|
191
|
+
return pullRepositoryTarget(selectedTarget, {workspaceRoot: repositoryDirectory, signal, onEvent, run: execute});
|
|
181
192
|
},
|
|
182
193
|
signal
|
|
183
194
|
);
|
|
@@ -190,7 +201,7 @@ export function createRepositoryWorkspace(
|
|
|
190
201
|
return accept(
|
|
191
202
|
async function pushRepository() {
|
|
192
203
|
await prepare(signal);
|
|
193
|
-
return
|
|
204
|
+
return pushRepositoryTarget(selectedTarget, {workspaceRoot: repositoryDirectory, signal, onEvent, run: execute});
|
|
194
205
|
},
|
|
195
206
|
signal
|
|
196
207
|
);
|
|
@@ -236,7 +247,7 @@ export function createRepositoryWorkspace(
|
|
|
236
247
|
async function writeRepositoryFiles() {
|
|
237
248
|
const outcome = {
|
|
238
249
|
directory: repositoryDirectory,
|
|
239
|
-
...(selectedTarget === undefined ? {} : {target: {...selectedTarget}}),
|
|
250
|
+
...(selectedTarget === undefined ? {} : {target: {...selectedTarget.target}}),
|
|
240
251
|
state: 'local',
|
|
241
252
|
stage: 'prepare',
|
|
242
253
|
paths: selectedFiles.map(function selectedPath(file) { return file.path; }),
|