arcane-os 0.92.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 +17 -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/inventory/package-api.json +3 -3
- package/docs/reference/native-diagnostics.md +167 -0
- 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-native-diagnostics-source.mjs +13 -0
- package/src/import-map.mjs +1 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,22 @@
|
|
|
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
|
+
|
|
3
20
|
## 0.92.0
|
|
4
21
|
|
|
5
22
|
- Add opt-in `allowUnborn` Git text snapshots and `initialBranch` repository
|
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
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"schemaVersion": 1,
|
|
3
|
-
"sdkVersion": "0.
|
|
3
|
+
"sdkVersion": "0.92.0",
|
|
4
4
|
"environment": {
|
|
5
5
|
"runtime": "Node.js for Node entrypoints; browser for browser-only entrypoints",
|
|
6
6
|
"minimumVersion": "22.23.2 for Node entrypoints",
|
|
@@ -4304,7 +4304,7 @@
|
|
|
4304
4304
|
"summary": "Describes existing Core method input, output and semantic shapes.",
|
|
4305
4305
|
"availability": "Node and browser/bundler",
|
|
4306
4306
|
"protocol": "arcane/1 method descriptions",
|
|
4307
|
-
"normalization": "Descriptive data only, including independent localai.devices inventory and localai.resolveTarget physical-selection records; presence does not establish engine support or observed execution. The active host and its registered services remain the authority for method availability"
|
|
4307
|
+
"normalization": "Descriptive data only, including independent localai.devices inventory and localai.resolveTarget physical-selection records; presence does not establish engine support or observed execution. Native notifications.status/show/state/close describe actual host support, exact caller content, retained host-lifetime outcomes and explicit owned removal; native submission does not establish display or human receipt. The active host and its registered services remain the authority for method availability"
|
|
4308
4308
|
},
|
|
4309
4309
|
{
|
|
4310
4310
|
"id": "core-contracts:CoreError",
|
|
@@ -4360,7 +4360,7 @@
|
|
|
4360
4360
|
"summary": "Projects a Core client into the established application-facing Arcane namespace.",
|
|
4361
4361
|
"availability": "Browser and native WebView JavaScript hosts with a Core client",
|
|
4362
4362
|
"protocol": "arcane/1 compatibility facade",
|
|
4363
|
-
"normalization": "Routes namespace methods through client.invoke while retaining operation-specific parameters, correlation, streaming callbacks, timeouts and cancellation; window.state({signal}?) reads the requesting window and window.setState({state},{signal}?) selects normal, maximized or fullscreen. The matching Windows host returns {platform:'windows',supported:true,state}, including observed minimized state, restores normal bounds/frame controls on leaving fullscreen and prevents activation/focus assignment during visible state transitions without changing TopMost, Core, origin or profile. Saved Settings remain application-owned; cancellation does not undo an applied change and unavailable hosts retain their actual errors without fallback. window.setTheme(presentation,{signal}?) forwards the complete per-window backgroundColor/textColor record, with omitted fields unchanged and null reset, and returns the host's actual applied/unsupported result without changing system appearance"
|
|
4363
|
+
"normalization": "Routes namespace methods through client.invoke while retaining operation-specific parameters, correlation, streaming callbacks, timeouts and cancellation; window.state({signal}?) reads the requesting window and window.setState({state},{signal}?) selects normal, maximized or fullscreen. The matching Windows host returns {platform:'windows',supported:true,state}, including observed minimized state, restores normal bounds/frame controls on leaving fullscreen and prevents activation/focus assignment during visible state transitions without changing TopMost, Core, origin or profile. Saved Settings remain application-owned; cancellation does not undo an applied change and unavailable hosts retain their actual errors without fallback. window.setTheme(presentation,{signal}?) forwards the complete per-window backgroundColor/textColor record, with omitted fields unchanged and null reset, and returns the host's actual applied/unsupported result without changing system appearance. notifications.status/show/state/close forward complete caller requests and standard invoke options; status normalizes only METHOD_NOT_ALLOWED with core-namespace-unavailable while retaining the complete original error. Matching native window hosts retain complete notification records and ordered revisions across renderer navigation, with notifications.state events and subscribe-before-snapshot recovery. Submitted means native Show returned, not displayed or read; cancellation after submission does not remove an accepted notice, and explicit close retains its record. Recipient, text, trigger, deduplication and navigation policy remains application-owned"
|
|
4364
4364
|
},
|
|
4365
4365
|
{
|
|
4366
4366
|
"id": "core-client:installCoreClient",
|