arcane-os 0.91.0 → 0.93.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 +33 -0
- package/README.md +1 -1
- package/browser-runtime/core/client.mjs +18 -0
- package/browser-runtime/core/contracts.mjs +20 -0
- package/browser-runtime/core/native-diagnostics.mjs +217 -0
- package/docs/reference/core/arcane-api.md +32 -0
- package/docs/reference/core/arcane-events.md +25 -0
- package/docs/reference/core-client.md +99 -0
- package/docs/reference/core-repositories.md +87 -4
- package/docs/reference/git-text-snapshot.md +47 -3
- package/docs/reference/inventory/package-api.json +7 -7
- package/docs/reference/native-diagnostics.md +167 -0
- package/docs/reference/sdk-api.md +52 -7
- package/package.json +3 -1
- package/src/core/app-control.mjs +1 -0
- package/src/core/hosts/windows/ArcaneAppControl.cs +1 -1
- package/src/core/hosts/windows/ArcaneHost.cs +167 -0
- package/src/core/hosts/windows/ArcaneLauncher.cs +1 -0
- package/src/core/hosts/windows/ArcaneNativeDiagnostics.cs +137 -0
- package/src/core/hosts/windows/ArcaneNotifications.cs +822 -0
- package/src/core/hosts/windows/ArcaneWindowControl.cs +3 -2
- package/src/core/repositories.mjs +60 -3
- package/src/core-native-diagnostics-source.mjs +13 -0
- package/src/git-text-snapshot.mjs +25 -5
- package/src/import-map.mjs +1 -0
- package/src/process.mjs +25 -1
- package/src/repository.mjs +25 -1
- package/src/windows-git-executable.mjs +103 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,38 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.93.0
|
|
4
|
+
|
|
5
|
+
- Add native desktop notification status, submission, state and removal through
|
|
6
|
+
`Arcane.notifications`, with ordered notification state events and an owned
|
|
7
|
+
Windows adapter. The host reports actual submission and OS errors; submission
|
|
8
|
+
does not establish that a person saw or activated the notification.
|
|
9
|
+
- Retain complete native document errors in the Windows host session, including
|
|
10
|
+
`window.error`, `unhandledrejection` and explicit `reportNativeError()` reports.
|
|
11
|
+
Retrieve non-consuming records through `connectAppControl().diagnostics()`
|
|
12
|
+
without foregrounding the app or waiting for Core/model readiness. Records
|
|
13
|
+
survive document navigation within that host session and end with the host.
|
|
14
|
+
- Publish the matching rebuilt Windows host archive with its document-start
|
|
15
|
+
diagnostic helper. Keep ordinary console output separate from retained error
|
|
16
|
+
history; console interception and caught console arguments are outside this
|
|
17
|
+
release. Native notification behavior and application acceptance remain
|
|
18
|
+
consumer-owned verification, distinct from the selected compile and package.
|
|
19
|
+
|
|
20
|
+
## 0.92.0
|
|
21
|
+
|
|
22
|
+
- Add opt-in `allowUnborn` Git text snapshots and `initialBranch` repository
|
|
23
|
+
preparation for first publication from a remote with no advertised revision.
|
|
24
|
+
Repository configuration now reports the actual revision and unborn state.
|
|
25
|
+
Preserve existing checkouts, advertised refs, complete diagnostics and real
|
|
26
|
+
Git failures; first publication uses the existing explicit non-force writer.
|
|
27
|
+
- Let the default Windows `git` process runner discover an installed Git for
|
|
28
|
+
Windows registration and append its command directory only to that child's
|
|
29
|
+
PATH. Existing lookup priority, other platforms, explicit executable paths
|
|
30
|
+
and custom adapters retain their behavior; no installation, global setting
|
|
31
|
+
change or command retry is introduced.
|
|
32
|
+
- Reuse the retained Windows host, Whisper and ONNX runtime archives. This
|
|
33
|
+
release changes shared JavaScript repository/process behavior, not those
|
|
34
|
+
native binaries; application adoption remains independently owned.
|
|
35
|
+
|
|
3
36
|
## 0.91.0
|
|
4
37
|
|
|
5
38
|
- Persist complete native `modelAssets.prepare()` acquisitions across release,
|
package/README.md
CHANGED
|
@@ -25,7 +25,7 @@ selected SDK projection or an explicit live SDK source mount. Both profiles
|
|
|
25
25
|
share the theme, packaging, event, cancellation,
|
|
26
26
|
and browser run contracts while retaining their selected application layout.
|
|
27
27
|
|
|
28
|
-
This checkout defines the `0.
|
|
28
|
+
This checkout defines the `0.93.0` SDK contract. Development applications declare
|
|
29
29
|
`arcane-os` as `latest` and retain their lockfile. Exact SDK pins begin when Roshi
|
|
30
30
|
designates a production build. Follow [Consumer updates](docs/consumer-updates.md)
|
|
31
31
|
to refresh and compare the installed SDK and lockfile with the published channel;
|
|
@@ -646,6 +646,24 @@ export function createCoreFacade(client){
|
|
|
646
646
|
storage:{list:()=>invoke('storage.list'),get:key=>invoke('storage.get',{key}),set:(key,value)=>invoke('storage.set',{key,value}),delete:key=>invoke('storage.delete',{key})},
|
|
647
647
|
preferences:{list:()=>invoke('preferences.list'),get:key=>invoke('preferences.get',{key}),set:(key,value)=>invoke('preferences.set',{key,value}),
|
|
648
648
|
setMany:entries=>invoke('preferences.setMany',{entries}),delete:key=>invoke('preferences.delete',{key})},
|
|
649
|
+
notifications: {
|
|
650
|
+
status(options) {
|
|
651
|
+
return invoke('notifications.status', {}, options).catch(function notificationStatusUnavailable(error) {
|
|
652
|
+
if (error?.code !== 'METHOD_NOT_ALLOWED' || error?.reason !== 'core-namespace-unavailable') throw error;
|
|
653
|
+
return {supported: false, available: false, permissionDisabled: null,
|
|
654
|
+
reason: 'host-notifications-unavailable', error};
|
|
655
|
+
});
|
|
656
|
+
},
|
|
657
|
+
show(request, options) {
|
|
658
|
+
return invoke('notifications.show', request, options);
|
|
659
|
+
},
|
|
660
|
+
state(selection = {}, options) {
|
|
661
|
+
return invoke('notifications.state', selection, options);
|
|
662
|
+
},
|
|
663
|
+
close(selection, options) {
|
|
664
|
+
return invoke('notifications.close', selection, options);
|
|
665
|
+
}
|
|
666
|
+
},
|
|
649
667
|
window: {
|
|
650
668
|
state(options) {
|
|
651
669
|
return invoke('window.state', {}, {signal: options?.signal});
|
|
@@ -43,6 +43,26 @@ export const CORE_METHOD_CONTRACTS={
|
|
|
43
43
|
output: '{requestedTarget,resolvedDevice,resolution,reason}',
|
|
44
44
|
meaning: 'Resolve a saved physical identity. Explicit null selects engine-owned automatic resolution; it does not claim a configured or observed device.'
|
|
45
45
|
},
|
|
46
|
+
'notifications.status': {
|
|
47
|
+
input: '{}',
|
|
48
|
+
output: '{supported,available,permissionDisabled,reason?,error?}',
|
|
49
|
+
meaning: 'Actual native host support and OS permission state; no preference changes or browser/audio substitution.'
|
|
50
|
+
},
|
|
51
|
+
'notifications.show': {
|
|
52
|
+
input: '{id,title,body,data}',
|
|
53
|
+
output: 'notification-record-v1',
|
|
54
|
+
meaning: 'Submit one exact application notification. Submitted means native Show returned, not display or human receipt. Caller IDs cannot be reused during this host lifetime.'
|
|
55
|
+
},
|
|
56
|
+
'notifications.state': {
|
|
57
|
+
input: '{id?}',
|
|
58
|
+
output: '{revision,notifications:notification-record-v1[]}',
|
|
59
|
+
meaning: 'Retained complete records for this application host lifetime, including actual terminal outcomes; accepted notices survive renderer navigation.'
|
|
60
|
+
},
|
|
61
|
+
'notifications.close': {
|
|
62
|
+
input: '{id}',
|
|
63
|
+
output: 'notification-record-v1',
|
|
64
|
+
meaning: 'Remove only the exact owned notice and retain its record. Cancellation after submission does not reverse human interaction.'
|
|
65
|
+
},
|
|
46
66
|
'window.state': {
|
|
47
67
|
input: '{}',
|
|
48
68
|
output: '{platform,supported,state}',
|
|
@@ -0,0 +1,217 @@
|
|
|
1
|
+
const NATIVE_DIAGNOSTICS_KEY = Symbol.for('arcane-os.native-diagnostics');
|
|
2
|
+
const NATIVE_DOCUMENT_KEY = Symbol.for('arcane-os.app-control.document');
|
|
3
|
+
|
|
4
|
+
/** Report an application-caught error to this document's native host ledger. */
|
|
5
|
+
export async function reportNativeError(error, options = {}) {
|
|
6
|
+
const owner = globalThis[NATIVE_DIAGNOSTICS_KEY];
|
|
7
|
+
if (!owner) throw nativeDiagnosticsUnavailable();
|
|
8
|
+
return owner.report(error, options);
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
/** The native host installs this once, at document creation, before app scripts. */
|
|
12
|
+
export function installNativeDiagnostics(global = globalThis) {
|
|
13
|
+
if (global[NATIVE_DIAGNOSTICS_KEY]) return global[NATIVE_DIAGNOSTICS_KEY];
|
|
14
|
+
|
|
15
|
+
const documentId = global[NATIVE_DOCUMENT_KEY] ??= global.crypto.randomUUID();
|
|
16
|
+
let delivery = Promise.resolve();
|
|
17
|
+
const owner = {
|
|
18
|
+
documentId,
|
|
19
|
+
ready: null,
|
|
20
|
+
report(error, options = {}) {
|
|
21
|
+
return capture('application', error, options);
|
|
22
|
+
}
|
|
23
|
+
};
|
|
24
|
+
global[NATIVE_DIAGNOSTICS_KEY] = owner;
|
|
25
|
+
global.addEventListener('error', captureWindowError);
|
|
26
|
+
global.addEventListener('unhandledrejection', captureUnhandledRejection);
|
|
27
|
+
owner.ready = transmit(envelope('document'));
|
|
28
|
+
owner.ready.catch(reportCaptureFailure);
|
|
29
|
+
return owner;
|
|
30
|
+
|
|
31
|
+
function envelope(type, record) {
|
|
32
|
+
return {
|
|
33
|
+
protocol: 'arcane.native-diagnostics/1',
|
|
34
|
+
type,
|
|
35
|
+
documentId,
|
|
36
|
+
documentUrl: global.location.href,
|
|
37
|
+
time: new Date().toISOString(),
|
|
38
|
+
...(record === undefined ? {} : {record})
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
async function capture(kind, error, options = {}, observation) {
|
|
43
|
+
// Snapshot synchronously at the reported failure, before any bridge wait.
|
|
44
|
+
const record = {kind, ...encodeDiagnosticValues(error, options)};
|
|
45
|
+
if (observation !== undefined) {
|
|
46
|
+
record.location = observation.location;
|
|
47
|
+
record.message = observation.message;
|
|
48
|
+
}
|
|
49
|
+
return transmit(envelope('error', record));
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function transmit(frame) {
|
|
53
|
+
// This document has one ordered ingress. A failed write is observed by
|
|
54
|
+
// its caller and does not prevent subsequent independent reports.
|
|
55
|
+
const text = JSON.stringify(frame);
|
|
56
|
+
const pending = delivery.then(async function sendDiagnostic() {
|
|
57
|
+
const windows = global.chrome?.webview?.hostObjects?.arcaneDiagnostics;
|
|
58
|
+
const webkit = global.webkit?.messageHandlers?.arcaneDiagnostics;
|
|
59
|
+
let reply;
|
|
60
|
+
if (windows) reply = await windows.Send(text);
|
|
61
|
+
else if (webkit) reply = await webkit.postMessage(text);
|
|
62
|
+
else throw nativeDiagnosticsUnavailable();
|
|
63
|
+
const result = typeof reply === 'string' ? JSON.parse(reply) : reply;
|
|
64
|
+
if (result?.accepted !== true) {
|
|
65
|
+
const error = new Error('The native host did not retain the diagnostic.', {cause: result?.error});
|
|
66
|
+
error.code = 'ARCANE_NATIVE_DIAGNOSTIC_REJECTED';
|
|
67
|
+
error.details = result;
|
|
68
|
+
throw error;
|
|
69
|
+
}
|
|
70
|
+
return result;
|
|
71
|
+
});
|
|
72
|
+
delivery = pending.catch(function observeOrderedFailure() {});
|
|
73
|
+
return pending;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
function captureWindowError(event) {
|
|
77
|
+
// Resource-element load failures are a different observation boundary;
|
|
78
|
+
// this listener does not inspect their DOM or request payloads.
|
|
79
|
+
if (event.target !== global) return;
|
|
80
|
+
capture('window.error', event.error, {},
|
|
81
|
+
{message: event.message, location: {file: event.filename, line: event.lineno, column: event.colno}}
|
|
82
|
+
).catch(function failedWindowCapture(failure) {
|
|
83
|
+
reportCaptureFailure(failure, event.error);
|
|
84
|
+
});
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
function captureUnhandledRejection(event) {
|
|
88
|
+
capture('unhandledrejection', event.reason).catch(function failedRejectionCapture(failure) {
|
|
89
|
+
reportCaptureFailure(failure, event.reason);
|
|
90
|
+
});
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
function reportCaptureFailure(failure, original) {
|
|
94
|
+
// Observe our own asynchronous failure without manufacturing another
|
|
95
|
+
// unhandled rejection. The browser's ordinary error behavior continues.
|
|
96
|
+
global.console.error('Arcane native diagnostic capture failed.', failure, original);
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
function nativeDiagnosticsUnavailable() {
|
|
101
|
+
const error = new Error('This document has no native diagnostic capture host.');
|
|
102
|
+
error.code = 'ARCANE_NATIVE_DIAGNOSTICS_UNAVAILABLE';
|
|
103
|
+
return error;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* Transport-local property graph, not JSON.stringify(error). Descriptor values
|
|
108
|
+
* preserve non-enumerable Error fields, symbols, cycles and shared references.
|
|
109
|
+
* Accessors remain descriptors; inspection never calls their get/set functions.
|
|
110
|
+
* An object that rejects reflection fails the report with that original error.
|
|
111
|
+
*/
|
|
112
|
+
function encodeDiagnosticValues(error, options) {
|
|
113
|
+
const references = new Map();
|
|
114
|
+
const pending = [];
|
|
115
|
+
const values = [];
|
|
116
|
+
const record = {valueFormat: 'arcane.diagnostic-value/1', error: encode(error), values};
|
|
117
|
+
const details = Object.getOwnPropertyDescriptor(options, 'details');
|
|
118
|
+
if (details) {
|
|
119
|
+
record.details = Object.hasOwn(details, 'value') ? encode(details.value)
|
|
120
|
+
: {type: 'accessor', get: encode(details.get), set: encode(details.set)};
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
for (let index = 0; index < pending.length; index += 1) {
|
|
124
|
+
const value = pending[index];
|
|
125
|
+
const node = values[index];
|
|
126
|
+
if (typeof value === 'symbol') {
|
|
127
|
+
node.description = encode(value.description);
|
|
128
|
+
node.globalKey = encode(Symbol.keyFor(value));
|
|
129
|
+
continue;
|
|
130
|
+
}
|
|
131
|
+
const descriptors = Object.getOwnPropertyDescriptors(value);
|
|
132
|
+
node.properties = Reflect.ownKeys(descriptors).map(function describeProperty(key) {
|
|
133
|
+
return {key: encode(key), ...encodeDescriptor(descriptors[key])};
|
|
134
|
+
});
|
|
135
|
+
const prototype = Object.getPrototypeOf(value);
|
|
136
|
+
const names = prototypeNames(prototype);
|
|
137
|
+
if (names.length) node.constructorName = names[0];
|
|
138
|
+
|
|
139
|
+
// Error subclasses may supply these on a prototype, including custom
|
|
140
|
+
// accessors. Preserve the descriptor without reading through it.
|
|
141
|
+
node.inherited = [];
|
|
142
|
+
for (const key of ['name', 'message', 'stack', 'cause', 'errors', 'code', 'details']) {
|
|
143
|
+
if (Object.hasOwn(descriptors, key)) continue;
|
|
144
|
+
const descriptor = findDescriptor(prototype, key);
|
|
145
|
+
if (descriptor) node.inherited.push({key, ...encodeDescriptor(descriptor)});
|
|
146
|
+
}
|
|
147
|
+
if (names.includes('Map')) {
|
|
148
|
+
node.entries = Array.from(Map.prototype.entries.call(value), function encodeEntry([key, item]) {
|
|
149
|
+
return [encode(key), encode(item)];
|
|
150
|
+
});
|
|
151
|
+
} else if (names.includes('Set')) {
|
|
152
|
+
node.entries = Array.from(Set.prototype.values.call(value), encode);
|
|
153
|
+
} else if (names.includes('Date')) {
|
|
154
|
+
node.time = encode(Date.prototype.getTime.call(value));
|
|
155
|
+
} else if (names.includes('RegExp')) {
|
|
156
|
+
node.source = Object.getOwnPropertyDescriptor(RegExp.prototype, 'source').get.call(value);
|
|
157
|
+
node.flags = {};
|
|
158
|
+
for (const key of ['hasIndices', 'global', 'ignoreCase', 'multiline', 'dotAll', 'unicode', 'unicodeSets', 'sticky']) {
|
|
159
|
+
const getter = Object.getOwnPropertyDescriptor(RegExp.prototype, key)?.get;
|
|
160
|
+
if (getter) node.flags[key] = getter.call(value);
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
return record;
|
|
165
|
+
|
|
166
|
+
function encode(value) {
|
|
167
|
+
const type = typeof value;
|
|
168
|
+
if (value === null || type === 'string' || type === 'boolean') return value;
|
|
169
|
+
if (type === 'number') {
|
|
170
|
+
return Number.isFinite(value) && !Object.is(value, -0)
|
|
171
|
+
? value : {type, value: Object.is(value, -0) ? '-0' : String(value)};
|
|
172
|
+
}
|
|
173
|
+
if (type === 'undefined') return {type};
|
|
174
|
+
if (type === 'bigint') return {type, value: String(value)};
|
|
175
|
+
if (!references.has(value)) {
|
|
176
|
+
const id = values.length + 1;
|
|
177
|
+
references.set(value, id);
|
|
178
|
+
pending.push(value);
|
|
179
|
+
values.push({id, type: Array.isArray(value) ? 'array' : type});
|
|
180
|
+
}
|
|
181
|
+
return {ref: references.get(value)};
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
function encodeDescriptor(descriptor) {
|
|
185
|
+
const result = {enumerable: descriptor.enumerable, configurable: descriptor.configurable};
|
|
186
|
+
if (Object.hasOwn(descriptor, 'value')) {
|
|
187
|
+
return {...result, value: encode(descriptor.value), writable: descriptor.writable};
|
|
188
|
+
}
|
|
189
|
+
return {...result, get: encode(descriptor.get), set: encode(descriptor.set)};
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
function findDescriptor(prototype, key) {
|
|
193
|
+
const visited = new Set();
|
|
194
|
+
while (prototype && !visited.has(prototype)) {
|
|
195
|
+
visited.add(prototype);
|
|
196
|
+
const descriptor = Object.getOwnPropertyDescriptor(prototype, key);
|
|
197
|
+
if (descriptor) return descriptor;
|
|
198
|
+
prototype = Object.getPrototypeOf(prototype);
|
|
199
|
+
}
|
|
200
|
+
return undefined;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
function prototypeNames(prototype) {
|
|
204
|
+
const names = [];
|
|
205
|
+
const visited = new Set();
|
|
206
|
+
while (prototype && !visited.has(prototype)) {
|
|
207
|
+
visited.add(prototype);
|
|
208
|
+
const constructor = Object.getOwnPropertyDescriptor(prototype, 'constructor')?.value;
|
|
209
|
+
if (typeof constructor === 'function') {
|
|
210
|
+
const name = Object.getOwnPropertyDescriptor(constructor, 'name')?.value;
|
|
211
|
+
if (typeof name === 'string') names.push(name);
|
|
212
|
+
}
|
|
213
|
+
prototype = Object.getPrototypeOf(prototype);
|
|
214
|
+
}
|
|
215
|
+
return names;
|
|
216
|
+
}
|
|
217
|
+
}
|
|
@@ -49,6 +49,7 @@ This inventory covers every enumerable, non-method member exposed directly or th
|
|
|
49
49
|
| `Arcane.storage` | Namespace | App-scoped native key/value storage. |
|
|
50
50
|
| `Arcane.preferences` | Namespace | App-scoped preference reads and atomic writes. |
|
|
51
51
|
| `Arcane.window` | Namespace | SDK host window presentation for the calling document; adapter availability is independent of the OS-user appearance service. |
|
|
52
|
+
| `Arcane.notifications` | Namespace | Native app-scoped desktop notifications and retained host-lifetime outcomes; see the [notification contract](../core-client.md#native-desktop-notifications). |
|
|
52
53
|
| `Arcane.appearance` | Namespace | Native appearance state and application. |
|
|
53
54
|
| `Arcane.session` | Namespace | Current Arcane session lifecycle. |
|
|
54
55
|
| `Arcane.provisioning` | Namespace | Provisioning-plan operations. |
|
|
@@ -394,6 +395,37 @@ product-neutral reference.
|
|
|
394
395
|
|
|
395
396
|
Terminal output and lifecycle changes are event-driven. The canonical [Arcane event inventory](arcane-events.md#event-inventory) defines `terminal.output`, `terminal.exit`, and the Android `terminal.error` payload. The shared `TerminalClient` adapter correlates those deliveries with owned sessions and re-emits DOM events for reusable UI code.
|
|
396
397
|
|
|
398
|
+
## Native desktop notifications
|
|
399
|
+
|
|
400
|
+
These methods use the actual application's native window bridge. Windows source
|
|
401
|
+
support is pending selected native build and OS delivery verification. Direct
|
|
402
|
+
shared-Core listeners and other unimplemented host adapters are unavailable;
|
|
403
|
+
there is no browser or audio substitute. The [complete contract](../core-client.md#native-desktop-notifications)
|
|
404
|
+
defines records, cancellation, duplicate IDs, permission reporting and recovery.
|
|
405
|
+
|
|
406
|
+
| Method | Parameters | Return | Description |
|
|
407
|
+
| --- | --- | --- | --- |
|
|
408
|
+
| `Arcane.notifications.status(options?)` | `options`: standard `signal`, `timeoutMs`, `onRequest` request options | `Promise<{supported,available,permissionDisabled,reason?,error?}>` | Reports actual host support and permission state without changing preferences. Only an exact unavailable Core namespace is normalized, retaining its original complete error. |
|
|
409
|
+
| `Arcane.notifications.show(request, options?)` | `request`: `{id,title,body,data}`; `options`: standard request options | `Promise<NotificationRecord>` | Submits exact title/body and opaque JSON-compatible data for the application's stable ID. Native submission is distinct from visual display or human receipt; a reused ID rejects with the existing record. |
|
|
410
|
+
| `Arcane.notifications.state(selection?, options?)` | `selection`: `{id?}`, defaults to `{}`; `options`: standard request options | `Promise<{revision,notifications:NotificationRecord[]}>` | Returns complete retained records for the current host lifetime. Subscribe to `notifications.state` first and merge by revision. |
|
|
411
|
+
| `Arcane.notifications.close(selection, options?)` | `selection`: `{id}`; `options`: standard request options | `Promise<NotificationRecord>` | Removes the exact owned notice and retains its record; no implicit resend or replacement. |
|
|
412
|
+
|
|
413
|
+
`NotificationRecord` contains `{id,title,body,data,state,revision}` plus actual
|
|
414
|
+
timestamps, reasons and complete errors. `event` identifies the latest observed
|
|
415
|
+
event and `events` retains its ordered history; activation can race removal
|
|
416
|
+
without changing an already `closed` state. Accepted notices survive renderer
|
|
417
|
+
navigation. Terminal callbacks remain retained; timeout dismissal does not mean
|
|
418
|
+
Notification Center removal. Recipients, triggers, text, deduplication and
|
|
419
|
+
navigation belong to the application. Cross-process persistence and cold-start
|
|
420
|
+
activation are outside this contract.
|
|
421
|
+
|
|
422
|
+
Windows keeps a stable native identity per WebView profile so the OS retains
|
|
423
|
+
that profile's notification preferences. The SDK persists registration metadata
|
|
424
|
+
only; its notification text/data records remain in host memory. A second
|
|
425
|
+
simultaneous notifier for the same profile reports
|
|
426
|
+
`notification-owner-already-running`; distinct profiles remain independent.
|
|
427
|
+
Live COM ownership and notices drain at host shutdown.
|
|
428
|
+
|
|
397
429
|
## Platform, installation, users, and system
|
|
398
430
|
|
|
399
431
|
| Method | Parameters | Return | Description |
|
|
@@ -26,6 +26,31 @@ Every subscription method returns an unsubscribe function. Call it when the owni
|
|
|
26
26
|
| `terminal.exit` | Live, future-only terminal session notification. | Core-backed Arcane Terminal and Android Arcane Terminal hosts. | A terminal process exits and the host removes its session. | `sessionId` identifies the terminal session; `exitCode` is the process exit code when available; `signal` is the terminating signal name or `null`. |
|
|
27
27
|
| `terminal.error` | Live, future-only terminal session notification. | Android Arcane Terminal hosts. | Android stops a session at its output limit or cannot read one of its output streams. | `sessionId` identifies the terminal session; `message` explains the output-limit or stream-reading failure. |
|
|
28
28
|
| `appearance.changed` | Live, future-only host notification. | Microsoft NT WebView2 hosts. | The host observes an operating-system appearance change. | `scheme` is the configured host scheme; `effectiveScheme` is the resolved `dark` or `light` scheme; `source` is `windows`. |
|
|
29
|
+
| `notifications.state` | Live, future-only complete notification record; retained state is read separately. | SDK Windows application-window adapter; native build and OS delivery verification pending. Other unimplemented hosts and direct shared-Core listeners are unavailable. | An owned native notification changes submission, activation, dismissal, closure or failure state. | Complete `{id,title,body,data,state,revision}` record plus actual timestamps, reasons and errors. Caller `id` correlates the record; `revision` orders updates. Native submission is not a display or human-receipt acknowledgement. |
|
|
30
|
+
|
|
31
|
+
### Recover native notification state
|
|
32
|
+
|
|
33
|
+
Subscribe to `notifications.state` before calling `Arcane.notifications.state()`
|
|
34
|
+
and merge each record by revision. The event is not a durable completion event
|
|
35
|
+
for `when()`. A later renderer reads the host's retained records, including
|
|
36
|
+
actual terminal outcomes; accepted notices survive navigation. Retire old-client
|
|
37
|
+
subscriptions and pending snapshots when the installed client changes.
|
|
38
|
+
The record's `event` names its latest observation and `events` retains complete
|
|
39
|
+
ordered history. A raced `event:'activated'` can accompany `state:'closed'`;
|
|
40
|
+
the application decides how to route that actual activation.
|
|
41
|
+
|
|
42
|
+
Windows profile identity/shortcut registration persists across launches to
|
|
43
|
+
preserve OS preferences; the SDK's notification text, data and event records
|
|
44
|
+
remain in host memory for the current lifetime. A second simultaneous notifier
|
|
45
|
+
for that profile is unavailable with `notification-owner-already-running`;
|
|
46
|
+
other profiles are independent. Shutdown drains owned notices and live COM
|
|
47
|
+
ownership.
|
|
48
|
+
|
|
49
|
+
`submitted` means native `Show` returned. Later failure remains possible, and a
|
|
50
|
+
timeout dismissal does not establish removal from Notification Center. Explicit
|
|
51
|
+
`Arcane.notifications.close({id})` removes the owned notice while retaining its
|
|
52
|
+
record. See the [complete notification contract](../core-client.md#native-desktop-notifications)
|
|
53
|
+
for cancellation, permissions, exact content and host-lifetime limits.
|
|
29
54
|
|
|
30
55
|
### Subscribe and clean up
|
|
31
56
|
|
|
@@ -284,6 +284,105 @@ forwards the app's computed `--background` and `--text-color` in native hosts.
|
|
|
284
284
|
Applications using that owner need no duplicate theme listener. Ordinary
|
|
285
285
|
browsers keep their CSS presentation without making a native request.
|
|
286
286
|
|
|
287
|
+
## Native desktop notifications
|
|
288
|
+
|
|
289
|
+
`Arcane.notifications` uses the existing Core request transport to the actual
|
|
290
|
+
application window's native host. The Windows adapter is source implementation;
|
|
291
|
+
selected native build and actual OS delivery verification remain pending. A
|
|
292
|
+
matching published host is required for delivery. The portable facade is the
|
|
293
|
+
same on Windows, Linux and macOS, with Android requiring a host adapter; other
|
|
294
|
+
unimplemented hosts and direct shared-Core listener connections are unavailable.
|
|
295
|
+
Those listener connections bypass the window bridge. There is no browser
|
|
296
|
+
`Notification`, audio or simulated delivery substitute.
|
|
297
|
+
|
|
298
|
+
| Method | Parameters | Result |
|
|
299
|
+
| --- | --- | --- |
|
|
300
|
+
| `status(options?)` | Request options only | Actual native `supported`, `available` and `permissionDisabled`, with the host's reason/error when present |
|
|
301
|
+
| `show(request, options?)` | `{id,title,body,data}` | Complete notification record for the actual submission outcome |
|
|
302
|
+
| `state(selection = {}, options?)` | Optional `{id}` selects one owned ID; omission selects all retained records | `{revision,notifications:[complete records]}` |
|
|
303
|
+
| `close(selection, options?)` | `{id}` | Complete record after the exact owned notice's removal operation |
|
|
304
|
+
|
|
305
|
+
Every method accepts the standard `invoke` options `signal`, `timeoutMs` and
|
|
306
|
+
`onRequest({requestId})`. Requests, title/body strings and opaque JSON-compatible
|
|
307
|
+
`data` pass through unchanged. The application owns recipients, triggers, text,
|
|
308
|
+
deduplication and navigation. The OS controls presentation; preserving the
|
|
309
|
+
complete request does not establish that every character was visibly displayed.
|
|
310
|
+
|
|
311
|
+
`status()` changes no OS permissions or preferences. Only the exact Core error
|
|
312
|
+
`METHOD_NOT_ALLOWED` with `reason:'core-namespace-unavailable'` becomes
|
|
313
|
+
`{supported:false,available:false,permissionDisabled:null,
|
|
314
|
+
reason:'host-notifications-unavailable',error}`; `error` is the original complete
|
|
315
|
+
error and no platform is invented. All other invocation rejections propagate
|
|
316
|
+
unchanged. The native adapter can return an unavailable result with the complete
|
|
317
|
+
OS initialization or settings error. `permissionDisabled:null` means the permission state is
|
|
318
|
+
unknown. Use this status operation to discover the window adapter; Core's
|
|
319
|
+
service capability list does not describe host-intercepted notification methods.
|
|
320
|
+
Native `supported:null` means initialization could not establish OS support;
|
|
321
|
+
`available:false` retains its actual reason and complete error when supplied.
|
|
322
|
+
|
|
323
|
+
A retained record contains `{id,title,body,data,state,revision}` and the actual
|
|
324
|
+
timestamps, reasons and complete errors supplied by its owner. `submitted` means
|
|
325
|
+
the native `Show` call returned. It does not establish visible display or human
|
|
326
|
+
receipt. Later activation, dismissal and failure remain observable and retained.
|
|
327
|
+
A dismissal due to timeout does not establish removal from Notification Center.
|
|
328
|
+
Use `close({id})` for explicit OS removal. Closing retains the record; showing
|
|
329
|
+
the same caller ID again during this host lifetime rejects with its existing
|
|
330
|
+
record instead of resending or replacing it.
|
|
331
|
+
The top-level `event` names the latest observation, while `events` retains the
|
|
332
|
+
complete ordered history. An activation racing successful removal can report
|
|
333
|
+
`event:'activated'` with `state:'closed'`; the application owns action routing.
|
|
334
|
+
|
|
335
|
+
The live `notifications.state` event carries one complete updated record. It is
|
|
336
|
+
future-only. Subscribe before requesting the snapshot, then merge records by
|
|
337
|
+
their revisions so a delayed snapshot cannot overwrite a newer event:
|
|
338
|
+
|
|
339
|
+
```js
|
|
340
|
+
const records = new Map();
|
|
341
|
+
function receiveNotification(record) {
|
|
342
|
+
const previous = records.get(record.id);
|
|
343
|
+
if (!previous || record.revision >= previous.revision) records.set(record.id, record);
|
|
344
|
+
}
|
|
345
|
+
const stop = Arcane.events.on('notifications.state', receiveNotification);
|
|
346
|
+
try {
|
|
347
|
+
const snapshot = await Arcane.notifications.state();
|
|
348
|
+
for (const record of snapshot.notifications) receiveNotification(record);
|
|
349
|
+
} catch (error) {
|
|
350
|
+
stop();
|
|
351
|
+
throw error;
|
|
352
|
+
}
|
|
353
|
+
// Call stop() when this observation ends; it does not remove native notices.
|
|
354
|
+
```
|
|
355
|
+
|
|
356
|
+
When the installed Core client changes, detach the old event listener and ignore
|
|
357
|
+
its outstanding snapshot response. Recreate the records map for the new
|
|
358
|
+
client/host lifetime so an earlier host's revisions cannot suppress its records.
|
|
359
|
+
Subscribe and read the new client's snapshot
|
|
360
|
+
through [installation observation](#observe-installation-and-retirement).
|
|
361
|
+
Accepted notices and their records survive renderer navigation within the same
|
|
362
|
+
native host. There is no persisted cross-process recovery or cold-process
|
|
363
|
+
activation contract.
|
|
364
|
+
|
|
365
|
+
Windows reuses an SDK-created native identity and shortcut for the existing
|
|
366
|
+
WebView profile across launches, preserving that profile's OS notification
|
|
367
|
+
preferences. Its owned `AppUserModelId` registration stores the application's
|
|
368
|
+
display name, icon and live activator identity without changing notification
|
|
369
|
+
preferences or registering a cold-start launch command.
|
|
370
|
+
The SDK persists registration metadata only; its notification text
|
|
371
|
+
and data records remain in host memory. OS notification retention remains
|
|
372
|
+
platform-owned.
|
|
373
|
+
Distinct profiles have independent notification settings. One live notifier
|
|
374
|
+
owns a profile's COM activation registration; a second simultaneous host using
|
|
375
|
+
that profile reports `available:false` with
|
|
376
|
+
`reason:'notification-owner-already-running'` and may retry explicitly later.
|
|
377
|
+
Shutdown releases live COM ownership and owned notices while preserving the
|
|
378
|
+
identity registration for a later launch.
|
|
379
|
+
|
|
380
|
+
Cancellation observed before submission prevents that pending show from
|
|
381
|
+
submitting. After submission, request cancellation or renderer retirement cannot
|
|
382
|
+
reverse human interaction and does not remove the accepted notice; `close` is
|
|
383
|
+
explicit. Host shutdown drains owned operations, removes owned notices and
|
|
384
|
+
detaches callbacks, surfacing complete cleanup errors.
|
|
385
|
+
|
|
287
386
|
## Events and request lifetime
|
|
288
387
|
|
|
289
388
|
`events.on(name, listener)`, `once(name, listener)` and
|
|
@@ -47,7 +47,7 @@ const status = await repository.status({signal});
|
|
|
47
47
|
await repository.close();
|
|
48
48
|
```
|
|
49
49
|
|
|
50
|
-
`createRepositoryWorkspace({name,directory,dataRoot,remote,remoteBase,branch,longPaths,cloneIdentity,gitIdentity,onEvent,run}={})`
|
|
50
|
+
`createRepositoryWorkspace({name,directory,dataRoot,remote,remoteBase,branch,initialBranch,longPaths,cloneIdentity,gitIdentity,onEvent,run}={})`
|
|
51
51
|
returns `{directory,open,status,configuration,pull,push,write,close,drain,dispose}`. The `directory`
|
|
52
52
|
property is the absolute selected working path.
|
|
53
53
|
|
|
@@ -63,6 +63,10 @@ property is the absolute selected working path.
|
|
|
63
63
|
`branch`, when supplied, is passed unchanged to Git's `clone --branch` option;
|
|
64
64
|
otherwise Git selects the remote's default branch. Existing checkouts keep
|
|
65
65
|
their own branch, remote, tracked files, untracked files and local changes.
|
|
66
|
+
- Optional `initialBranch` names the branch for first publication from a new
|
|
67
|
+
unborn clone. It is separate from `branch`, which selects an existing remote
|
|
68
|
+
branch or tag. Omit `branch` when the remote has no advertised revision.
|
|
69
|
+
See [first publication](#first-publication-from-an-unborn-remote).
|
|
66
70
|
- Supplying `remoteBase` explicitly declares this initial-clone `remote` to be
|
|
67
71
|
a local filesystem locator. Its native absolute command argument is captured
|
|
68
72
|
during construction. Omission leaves Git's original string interpretation
|
|
@@ -77,7 +81,11 @@ property is the absolute selected working path.
|
|
|
77
81
|
keep Git's ordinary inheritance; existing checkouts retain their configuration.
|
|
78
82
|
See [clone-local author selection](#clone-local-author-selection).
|
|
79
83
|
- `onEvent` and optional `run` use the existing SDK process owner. The default
|
|
80
|
-
adapter is `runProcess`; Git
|
|
84
|
+
adapter is `runProcess`; Git can be available on PATH or through an installed
|
|
85
|
+
Git for Windows registration. The default Windows runner appends that
|
|
86
|
+
installation's `cmd` directory only to the Git child's PATH, preserving
|
|
87
|
+
existing command lookup priority. See [Windows Git discovery](sdk-api.md#runprocess).
|
|
88
|
+
Custom `run` adapters retain their own execution behavior. Credentials and
|
|
81
89
|
authentication remain with Git and the native host. The SDK adds no download,
|
|
82
90
|
credential store, polling, network retry or process supervisor.
|
|
83
91
|
|
|
@@ -147,6 +155,8 @@ Its result is:
|
|
|
147
155
|
{
|
|
148
156
|
repositoryRoot,
|
|
149
157
|
headRef: fullSymbolicRef | null,
|
|
158
|
+
revision: currentCommit | null,
|
|
159
|
+
unborn: boolean,
|
|
150
160
|
origin: {urls: [...], pushUrls: [...]},
|
|
151
161
|
upstream: {remoteNames: [...], mergeRefs: [...], urls: [...], pushUrls: [...]} | null
|
|
152
162
|
}
|
|
@@ -160,6 +170,16 @@ arrays contain that branch's configured `remote` and `merge` values; `urls` and
|
|
|
160
170
|
means the local repository and has no remote URL arrays. Origin is observed
|
|
161
171
|
independently, even when it is not the upstream.
|
|
162
172
|
|
|
173
|
+
`revision` and `unborn` come from Git's successful
|
|
174
|
+
[porcelain-v2 branch observation](https://git-scm.com/docs/git-status#_porcelain_format_version_2):
|
|
175
|
+
`(initial)` yields `revision:null, unborn:true`; a current commit yields that
|
|
176
|
+
opaque revision and `unborn:false`, including a commit with an empty tree and
|
|
177
|
+
detached HEAD. Symbolic HEAD alone does not establish an unborn branch.
|
|
178
|
+
The command uses `--no-optional-locks` and omits untracked-file discovery, so
|
|
179
|
+
this observation does not refresh the index on disk. A missing branch-state
|
|
180
|
+
record reports `ARCANE_REPOSITORY_HEAD_UNAVAILABLE`; unsuccessful commands
|
|
181
|
+
retain their actual failures rather than becoming an initial state.
|
|
182
|
+
|
|
163
183
|
Arrays retain configured order, repeated values, complete UTF-8 strings and
|
|
164
184
|
empty strings. `[]` means unset; `['']` means an explicit empty value. The
|
|
165
185
|
reader honors configured includes and ordinary Git configuration precedence.
|
|
@@ -172,8 +192,8 @@ Metadata uses the shared raw-Git-text reader. Lifecycle/stderr events remain
|
|
|
172
192
|
with the process owner, raw stdout remains parser-owned, and undecodable text
|
|
173
193
|
reports `ARCANE_GIT_CONFIGURATION_NOT_TEXT`. Failures retain complete raw
|
|
174
194
|
stdout, process diagnostics and causal errors using the identity reader's
|
|
175
|
-
documented attachment behavior. One observation uses
|
|
176
|
-
commands for an attached local branch, or
|
|
195
|
+
documented attachment behavior. One observation uses five read-only Git
|
|
196
|
+
commands for an attached local branch, or four otherwise. Results are not a
|
|
177
197
|
transaction with later work or external Git activity. The app owns any decision
|
|
178
198
|
that the current checkout corresponds to its selected repository and branch.
|
|
179
199
|
|
|
@@ -212,6 +232,69 @@ observe, refresh, write and publish. See Git's [pull](https://git-scm.com/docs/g
|
|
|
212
232
|
[push](https://git-scm.com/docs/git-push) and [configuration](https://git-scm.com/docs/git-config)
|
|
213
233
|
contracts.
|
|
214
234
|
|
|
235
|
+
### First publication from an unborn remote
|
|
236
|
+
|
|
237
|
+
An opt-in [snapshot refresh](git-text-snapshot.md#unborn-remotes) can report
|
|
238
|
+
`{revision:null,files:[],unborn:true}` after a successful complete empty remote
|
|
239
|
+
advertisement. This means no revision was advertised to this connection; it
|
|
240
|
+
does not assert that hidden refs or unreachable objects cannot exist.
|
|
241
|
+
The application retains that explicit state instead of inventing a commit.
|
|
242
|
+
|
|
243
|
+
For an application-selected new connection with that state, prepare its first
|
|
244
|
+
branch without passing the existing-branch `clone --branch` option:
|
|
245
|
+
|
|
246
|
+
```javascript
|
|
247
|
+
const repository = createRepositoryWorkspace({
|
|
248
|
+
name: connection.name,
|
|
249
|
+
remote: connection.locator,
|
|
250
|
+
remoteBase: connection.localBaseDirectory,
|
|
251
|
+
initialBranch: 'main'
|
|
252
|
+
});
|
|
253
|
+
await repository.open({signal});
|
|
254
|
+
const observed = await repository.configuration({signal});
|
|
255
|
+
// The application matches observed.headRef, unborn and configured URLs to its connection.
|
|
256
|
+
const publication = await repository.write({
|
|
257
|
+
files: selectedFiles,
|
|
258
|
+
message: authoredCommitMessage,
|
|
259
|
+
target: {remote: connection.locator, remoteBase: connection.localBaseDirectory, ref: 'refs/heads/main'},
|
|
260
|
+
signal
|
|
261
|
+
});
|
|
262
|
+
```
|
|
263
|
+
|
|
264
|
+
`initialBranch` is captured during construction. After a successful new clone,
|
|
265
|
+
the SDK observes the complete remote advertisement using the clone's original
|
|
266
|
+
working-directory context and captured remote argument. Only empty successful
|
|
267
|
+
output followed by the new checkout's explicit initial state and successful
|
|
268
|
+
empty `git for-each-ref --format=%(refname)` observation selects
|
|
269
|
+
`HEAD` as `refs/heads/<initialBranch>` through
|
|
270
|
+
[Git symbolic-ref](https://git-scm.com/docs/git-symbolic-ref). Git owns branch-name
|
|
271
|
+
validity. Advertised HEAD, branches or tags keep ordinary clone selection;
|
|
272
|
+
an already committed clone and every existing destination remain unchanged.
|
|
273
|
+
Any local ref supplied by Git's template or hook behavior is preserved and
|
|
274
|
+
leaves HEAD unchanged, even when the remote advertises nothing.
|
|
275
|
+
Omission adds no process and preserves the previous open result.
|
|
276
|
+
|
|
277
|
+
For a new clone with `initialBranch`, `open` retains its clone `stdout`/`stderr`
|
|
278
|
+
and adds ordered `outputs:[{operation,stdout,stderr,code,signal}]`. Operations
|
|
279
|
+
are `clone`, `observeRemote`, `observeHead`, `observeRefs` and `selectInitialBranch`, only as
|
|
280
|
+
performed. A later preparation failure preserves completed outputs in
|
|
281
|
+
`error.details:{directory,cloned:true,outputs}` and the original complete
|
|
282
|
+
failure as `cause`; cancellation retains completed observations too. A writer
|
|
283
|
+
that prepares first retains these processes in its existing `stage:'prepare'`
|
|
284
|
+
output records. Failed preparation leaves the actual directory intact and does
|
|
285
|
+
not retry initialization or rename an existing checkout on the next call.
|
|
286
|
+
|
|
287
|
+
There is nothing to pull before the first commit of a confirmed new unborn
|
|
288
|
+
connection. Use the existing exact-file `write` and explicit non-force
|
|
289
|
+
`HEAD:refs/heads/main` publication directly. For a committed remote, retain the
|
|
290
|
+
ordinary selected-branch clone and targeted pull/write composition. An existing
|
|
291
|
+
local checkout, different local branch or intervening remote change needs the
|
|
292
|
+
application's own connection decision; an unborn observation is not permission
|
|
293
|
+
to discard or switch it. Observations and publication are not transactional:
|
|
294
|
+
concurrent remote changes, authentication failures and rejected pushes remain
|
|
295
|
+
actual errors. No pull failure is converted into success, and no retry, force,
|
|
296
|
+
rollback, global setting or fabricated revision is introduced.
|
|
297
|
+
|
|
215
298
|
### Relative local repository locators
|
|
216
299
|
|
|
217
300
|
`remoteBase` is an explicit local-path choice, not a hint for a URL parser.
|