arcane-os 0.1.0-dev.5
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 +154 -0
- package/COMMERCIAL-LICENSE.md +13 -0
- package/LICENSE +661 -0
- package/NOTICE +70 -0
- package/README.md +448 -0
- package/bin/arcane-test.mjs +741 -0
- package/bin/arcane.mjs +5 -0
- package/docs/architecture.md +234 -0
- package/docs/compatibility.md +36 -0
- package/docs/event-manager.md +166 -0
- package/docs/platform-targets.md +108 -0
- package/docs/publishing.md +203 -0
- package/docs/reference/README.md +117 -0
- package/docs/reference/arcane-ollama.md +288 -0
- package/docs/reference/availability-and-normalization.md +123 -0
- package/docs/reference/behavioral-testing.md +86 -0
- package/docs/reference/cli.md +569 -0
- package/docs/reference/core/README.md +62 -0
- package/docs/reference/core/arcane-ai-contracts.md +873 -0
- package/docs/reference/core/arcane-api.md +601 -0
- package/docs/reference/core/arcane-entities.md +65 -0
- package/docs/reference/core/arcane-events.md +134 -0
- package/docs/reference/core/ollama-module.md +181 -0
- package/docs/reference/core/reference/arcane-api/ai-and-ollama.md +1909 -0
- package/docs/reference/core/reference/arcane-api/applications-terminal-capabilities.md +1057 -0
- package/docs/reference/core/reference/arcane-api/core-and-events.md +320 -0
- package/docs/reference/core/reference/arcane-api/filesystem-storage-preferences-appearance.md +610 -0
- package/docs/reference/core/reference/arcane-api/namespaces.md +1157 -0
- package/docs/reference/core/reference/arcane-api/platform-installation-users-system.md +1423 -0
- package/docs/reference/core/reference/arcane-api/session-provisioning-diagnostics-development.md +315 -0
- package/docs/reference/event-manager.md +957 -0
- package/docs/reference/inventory/package-api.json +2632 -0
- package/docs/reference/inventory/runtime-components.json +934 -0
- package/docs/reference/inventory/runtime-entities.json +26 -0
- package/docs/reference/inventory/runtime-modules.json +1249 -0
- package/docs/reference/protocols.md +242 -0
- package/docs/reference/runtime-components.md +1098 -0
- package/docs/reference/runtime-entities.md +303 -0
- package/docs/reference/runtime-modules.md +2010 -0
- package/docs/reference/sdk-api.md +4901 -0
- package/docs/roadmap.md +79 -0
- package/docs/work-amplification.md +124 -0
- package/node_modules/event-pubsub/CHANGELOG.md +55 -0
- package/node_modules/event-pubsub/MIGRATION.md +70 -0
- package/node_modules/event-pubsub/README.md +363 -0
- package/node_modules/event-pubsub/SECURITY.md +37 -0
- package/node_modules/event-pubsub/index.js +141 -0
- package/node_modules/event-pubsub/licence +21 -0
- package/node_modules/event-pubsub/package.json +59 -0
- package/node_modules/strong-type/README.md +408 -0
- package/node_modules/strong-type/assets/strong-type-header.png +0 -0
- package/node_modules/strong-type/index.js +1151 -0
- package/node_modules/strong-type/licence +21 -0
- package/node_modules/strong-type/node.js +125 -0
- package/node_modules/strong-type/package.json +61 -0
- package/package.json +95 -0
- package/runtime/ARCANE_RUNTIME_RELEASE.json +791 -0
- package/runtime/arcane/components/app-bar.html +468 -0
- package/runtime/arcane/components/assistant-panel.html +715 -0
- package/runtime/arcane/components/calculator.html +8 -0
- package/runtime/arcane/components/chart.html +655 -0
- package/runtime/arcane/components/chat.html +1225 -0
- package/runtime/arcane/components/conversation-view.html +13 -0
- package/runtime/arcane/components/dashboard-config.html +341 -0
- package/runtime/arcane/components/data-maintenance.html +112 -0
- package/runtime/arcane/components/data-view.html +92 -0
- package/runtime/arcane/components/directory-picker.html +197 -0
- package/runtime/arcane/components/document-inspector.html +252 -0
- package/runtime/arcane/components/file-drop.html +264 -0
- package/runtime/arcane/components/file-inspector.html +293 -0
- package/runtime/arcane/components/file-manager.html +1715 -0
- package/runtime/arcane/components/header.html +142 -0
- package/runtime/arcane/components/integration-settings.html +14 -0
- package/runtime/arcane/components/local-ai-status.html +360 -0
- package/runtime/arcane/components/markdown-document.html +1048 -0
- package/runtime/arcane/components/markdown-editor.html +360 -0
- package/runtime/arcane/components/media-embed.html +8 -0
- package/runtime/arcane/components/modal.html +402 -0
- package/runtime/arcane/components/output-panel.html +259 -0
- package/runtime/arcane/components/preferences-form.html +135 -0
- package/runtime/arcane/components/record-timeline.html +105 -0
- package/runtime/arcane/components/relationship-board.html +116 -0
- package/runtime/arcane/components/screen-capture.html +8 -0
- package/runtime/arcane/components/source-code-viewer.html +441 -0
- package/runtime/arcane/components/source-explanation.html +124 -0
- package/runtime/arcane/components/speech.html +365 -0
- package/runtime/arcane/components/summary-strip.html +177 -0
- package/runtime/arcane/components/table.html +77 -0
- package/runtime/arcane/components/task-progress.html +282 -0
- package/runtime/arcane/components/terminal-workspace.html +65 -0
- package/runtime/arcane/components/theme-editor.html +41 -0
- package/runtime/arcane/components/theme-switcher.html +46 -0
- package/runtime/arcane/components/unified-inbox.html +20 -0
- package/runtime/arcane/components/voice-transcription.html +476 -0
- package/runtime/arcane/components/weather-widget.html +8 -0
- package/runtime/arcane/components/web-navigator.html +239 -0
- package/runtime/arcane/css/communications.css +1 -0
- package/runtime/arcane/css/dashboard-config.css +45 -0
- package/runtime/arcane/css/document-site.css +981 -0
- package/runtime/arcane/css/layout.css +438 -0
- package/runtime/arcane/css/primitives.css +321 -0
- package/runtime/arcane/css/theme.css +112 -0
- package/runtime/arcane/css/utility-workspace.css +1 -0
- package/runtime/arcane/entities/ApiModelRecord.js +20 -0
- package/runtime/arcane/entities/Calculation.js +13 -0
- package/runtime/arcane/entities/Chat.js +581 -0
- package/runtime/arcane/entities/CommunicationMessage.js +29 -0
- package/runtime/arcane/entities/CommunicationThread.js +21 -0
- package/runtime/arcane/entities/Document.js +10 -0
- package/runtime/arcane/entities/File.js +143 -0
- package/runtime/arcane/entities/Image.js +135 -0
- package/runtime/arcane/entities/IntentEnvelope.js +834 -0
- package/runtime/arcane/entities/Preference.js +83 -0
- package/runtime/arcane/entities/TWiNPolicyDecision.js +1092 -0
- package/runtime/arcane/entities/TerminalSession.js +46 -0
- package/runtime/arcane/entities/Theme.js +107 -0
- package/runtime/arcane/entities/User.js +1046 -0
- package/runtime/arcane/entities/Weather.js +23 -0
- package/runtime/arcane/img/arcane-os-everywhere.png +0 -0
- package/runtime/arcane/img/arrow-left.png +0 -0
- package/runtime/arcane/img/arrow-right.png +0 -0
- package/runtime/arcane/img/doc.svg +5 -0
- package/runtime/arcane/img/folder.svg +4 -0
- package/runtime/arcane/img/image.svg +5 -0
- package/runtime/arcane/img/refresh.png +0 -0
- package/runtime/arcane/img/send.svg +9 -0
- package/runtime/arcane/img/trash.svg +5 -0
- package/runtime/arcane/img/upload.svg +5 -0
- package/runtime/arcane/modules/AI.js +2048 -0
- package/runtime/arcane/modules/AIPreferenceRuntime.js +32 -0
- package/runtime/arcane/modules/AIPreferenceTuple.js +92 -0
- package/runtime/arcane/modules/AIResponseLength.js +42 -0
- package/runtime/arcane/modules/AIResponseURLPolicy.js +626 -0
- package/runtime/arcane/modules/AnsiText.js +53 -0
- package/runtime/arcane/modules/ApiModelDatabase.js +25 -0
- package/runtime/arcane/modules/AppDataScope.js +245 -0
- package/runtime/arcane/modules/AppearancePreferences.js +28 -0
- package/runtime/arcane/modules/ArcaneCommunicationBridge.js +22 -0
- package/runtime/arcane/modules/ArcaneNavigationPolicy.js +135 -0
- package/runtime/arcane/modules/ArcaneNetworkPolicy.js +255 -0
- package/runtime/arcane/modules/AsyncBoundary.js +161 -0
- package/runtime/arcane/modules/BrowserTestSuite.js +326 -0
- package/runtime/arcane/modules/CalculatorEngine.js +20 -0
- package/runtime/arcane/modules/CaseEvidenceIndexer.js +134 -0
- package/runtime/arcane/modules/ChartLibrary.js +35 -0
- package/runtime/arcane/modules/ChatRecords.js +13 -0
- package/runtime/arcane/modules/CommunicationAppController.js +43 -0
- package/runtime/arcane/modules/CommunicationHub.js +16 -0
- package/runtime/arcane/modules/CommunicationPreferences.js +13 -0
- package/runtime/arcane/modules/CommunicationProviderRegistry.js +16 -0
- package/runtime/arcane/modules/ComponentContracts.js +586 -0
- package/runtime/arcane/modules/ConfiguredAIChatSession.js +288 -0
- package/runtime/arcane/modules/ConversationActionItems.js +488 -0
- package/runtime/arcane/modules/ConversationClosingReport.js +274 -0
- package/runtime/arcane/modules/ConversationTimebox.js +527 -0
- package/runtime/arcane/modules/CoreLocalModelCatalog.js +255 -0
- package/runtime/arcane/modules/DBLS.js +171 -0
- package/runtime/arcane/modules/DBOPFS.js +1154 -0
- package/runtime/arcane/modules/DBOPFSWorker.js +116 -0
- package/runtime/arcane/modules/DataMaintenance.js +91 -0
- package/runtime/arcane/modules/DevelopmentWorkspace.js +74 -0
- package/runtime/arcane/modules/DirectoryPicker.js +109 -0
- package/runtime/arcane/modules/DocumentNavigation.js +223 -0
- package/runtime/arcane/modules/Errors.js +1025 -0
- package/runtime/arcane/modules/GifEncoder.js +29 -0
- package/runtime/arcane/modules/HTMLImport.js +114 -0
- package/runtime/arcane/modules/InMemoryCommunicationProvider.js +14 -0
- package/runtime/arcane/modules/IsolatedModelQuestionRunner.js +275 -0
- package/runtime/arcane/modules/LocalAIReadiness.js +870 -0
- package/runtime/arcane/modules/LocalAIReadinessController.js +156 -0
- package/runtime/arcane/modules/MD.js +111 -0
- package/runtime/arcane/modules/Mail.js +352 -0
- package/runtime/arcane/modules/MailTransport.mjs +180 -0
- package/runtime/arcane/modules/Marked.min.js +71 -0
- package/runtime/arcane/modules/MemoryRecords.js +44 -0
- package/runtime/arcane/modules/MessageAdvisory.js +38 -0
- package/runtime/arcane/modules/ModelDefinition.js +189 -0
- package/runtime/arcane/modules/Ollama.js +74 -0
- package/runtime/arcane/modules/OllamaModelIdentifier.js +21 -0
- package/runtime/arcane/modules/OllamaSettings.js +24 -0
- package/runtime/arcane/modules/OpenMeteoWeatherProvider.js +14 -0
- package/runtime/arcane/modules/PreferenceStore.js +109 -0
- package/runtime/arcane/modules/QRCode.min.js +1 -0
- package/runtime/arcane/modules/Questionnaire.js +61 -0
- package/runtime/arcane/modules/RecordLinkIndex.js +24 -0
- package/runtime/arcane/modules/RecordPassageIndex.js +222 -0
- package/runtime/arcane/modules/RecordReviewStore.js +94 -0
- package/runtime/arcane/modules/RevocableProjectionLedger.js +1623 -0
- package/runtime/arcane/modules/RiskSignalAnalyzer.js +37 -0
- package/runtime/arcane/modules/ScamRiskPolicy.js +62 -0
- package/runtime/arcane/modules/ScopedOPFSCache.js +183 -0
- package/runtime/arcane/modules/ScreenCapture.js +20 -0
- package/runtime/arcane/modules/SpeechPlayback.js +581 -0
- package/runtime/arcane/modules/StaticDocumentCatalog.js +1248 -0
- package/runtime/arcane/modules/SystemAppearance.js +20 -0
- package/runtime/arcane/modules/SystemPlatformPresentation.js +51 -0
- package/runtime/arcane/modules/SystemToolRegistry.js +28 -0
- package/runtime/arcane/modules/TerminalClient.js +52 -0
- package/runtime/arcane/modules/TerminalCommandRegistry.js +50 -0
- package/runtime/arcane/modules/ThemeBootstrap.js +26 -0
- package/runtime/arcane/modules/ThemeManager.js +131 -0
- package/runtime/arcane/modules/TimeGuard.js +149 -0
- package/runtime/arcane/modules/ToolCallRouter.js +83 -0
- package/runtime/arcane/modules/WaitForComponent.js +102 -0
- package/runtime/arcane/modules/YouTubeMedia.js +16 -0
- package/runtime/arcane/modules/uPlot.LICENSE.txt +21 -0
- package/runtime/arcane/modules/uPlot.iife.min.js +2 -0
- package/runtime/arcane/modules/uPlot.min.css +1 -0
- package/runtime/arcane/security/arcane-network-policy.json +6 -0
- package/runtime/strong-type/index.js +352 -0
- package/runtime/strong-type/licence +21 -0
- package/runtime/strong-type/package.json +45 -0
- package/schemas/arcane-app-bundle.schema.json +125 -0
- package/schemas/arcane-app.schema.json +329 -0
- package/schemas/arcane-lock.schema.json +86 -0
- package/schemas/arcane-package.schema.json +224 -0
- package/schemas/cli-event.schema.json +122 -0
- package/schemas/event-stack.schema.json +152 -0
- package/schemas/native-build-plan.schema.json +176 -0
- package/schemas/target-adapter.schema.json +117 -0
- package/src/app-descriptor.mjs +500 -0
- package/src/cli/main.mjs +561 -0
- package/src/constants.mjs +31 -0
- package/src/dev-server.mjs +718 -0
- package/src/doctor.mjs +315 -0
- package/src/dom-event-instrumentation.mjs +594 -0
- package/src/errors.mjs +75 -0
- package/src/event-manager.mjs +1342 -0
- package/src/event-queue.mjs +138 -0
- package/src/events.mjs +219 -0
- package/src/index.mjs +177 -0
- package/src/integrated-provider-loader.mjs +432 -0
- package/src/native-plan.mjs +698 -0
- package/src/native-provider-loader.mjs +1126 -0
- package/src/packager/core.mjs +2691 -0
- package/src/process.mjs +353 -0
- package/src/release-bundle.mjs +2523 -0
- package/src/repository.mjs +90 -0
- package/src/runtime.mjs +452 -0
- package/src/scaffold.mjs +380 -0
- package/src/targets/index.mjs +436 -0
- package/src/templates/assets/app-icon.png +0 -0
- package/src/templates/workspace-template.mjs +388 -0
- package/src/testing-loader.mjs +9 -0
- package/src/testing.mjs +427 -0
- package/src/toolchain.mjs +1335 -0
- package/src/update-check.mjs +307 -0
- package/src/workspace.mjs +449 -0
|
@@ -0,0 +1,1057 @@
|
|
|
1
|
+
# Arcane API applications, terminal, and capabilities guides
|
|
2
|
+
|
|
3
|
+
The terminal methods are available only to the application whose bound id is
|
|
4
|
+
`terminal`, with the `terminal.execute` grant, through an admitted Core or
|
|
5
|
+
Android host. Other applications do not receive this process-control authority
|
|
6
|
+
by default.
|
|
7
|
+
|
|
8
|
+
Before presenting terminal UI, feature-detect `Arcane.terminal`, then confirm
|
|
9
|
+
the application, grant, and exact method admission with
|
|
10
|
+
`Arcane.capabilities.list()`:
|
|
11
|
+
|
|
12
|
+
```javascript
|
|
13
|
+
const terminal = globalThis.Arcane?.terminal;
|
|
14
|
+
const access = await globalThis.Arcane?.capabilities?.list?.();
|
|
15
|
+
|
|
16
|
+
const terminalAvailable = Boolean(
|
|
17
|
+
terminal?.start
|
|
18
|
+
&& access?.app?.id === 'terminal'
|
|
19
|
+
&& access.grants.includes('terminal.execute')
|
|
20
|
+
&& access.methods.includes('terminal.start')
|
|
21
|
+
);
|
|
22
|
+
```
|
|
23
|
+
Rejected calls use `Arcane.Error`. Its `code`, `message`, and `resolution`
|
|
24
|
+
properties are the stable application-facing recovery fields.
|
|
25
|
+
|
|
26
|
+
## Arcane.app.current()
|
|
27
|
+
|
|
28
|
+
### Overview
|
|
29
|
+
|
|
30
|
+
`Arcane.app.current()` identifies the application to which the native bridge
|
|
31
|
+
session is bound. The host, not renderer input, selects this identity. Use it
|
|
32
|
+
for display and correlation; app-scoped storage and authority are already bound
|
|
33
|
+
by the host and cannot be changed by passing another id.
|
|
34
|
+
|
|
35
|
+
The method takes no parameters, is a repeatable read, requires no capability,
|
|
36
|
+
and is admitted on Core and Android hosts. It has no side effect and emits no
|
|
37
|
+
event.
|
|
38
|
+
|
|
39
|
+
### Result
|
|
40
|
+
|
|
41
|
+
The exact result has these eight properties:
|
|
42
|
+
|
|
43
|
+
| Property | Contract |
|
|
44
|
+
| --- | --- |
|
|
45
|
+
| `id` | Canonical lower-case application id, at most 64 characters. |
|
|
46
|
+
| `displayName` | Host-bound display name. |
|
|
47
|
+
| `type` | `"app"`, `"shell"`, or `"provisioner"`. |
|
|
48
|
+
| `entry` | Safe relative entry path, or `null`. |
|
|
49
|
+
| `version` | The bound application's semantic version. Shell and Provisioner use the OS bundle version; packaged apps own their version. |
|
|
50
|
+
| `securityMode` | `"publisher-verified"`, `"unsigned-local-test"`, or `"unverified"`. |
|
|
51
|
+
| `publisherTrustSource` | Publisher evidence for a publisher-verified host, otherwise `null`. |
|
|
52
|
+
| `revocationStatus` | Revocation evidence for a publisher-verified host, otherwise `null`. |
|
|
53
|
+
|
|
54
|
+
Android returns the immutable Shell or application-APK descriptor with
|
|
55
|
+
`securityMode: "unverified"` and both publisher evidence fields set to `null`.
|
|
56
|
+
That describes the current Android distribution; it is not a publisher-trust
|
|
57
|
+
claim.
|
|
58
|
+
|
|
59
|
+
### Errors and recovery
|
|
60
|
+
|
|
61
|
+
`ARCANE_TRANSPORT_UNAVAILABLE` means the page is not running through an Arcane
|
|
62
|
+
host. `METHOD_CONTRACT_OUTPUT_INVALID` means the host supplied an invalid
|
|
63
|
+
identity; stop and repair or reinstall the matching host/package rather than
|
|
64
|
+
guessing an application id.
|
|
65
|
+
|
|
66
|
+
### Example
|
|
67
|
+
|
|
68
|
+
```javascript
|
|
69
|
+
const currentApp = await Arcane.app.current();
|
|
70
|
+
|
|
71
|
+
console.log(currentApp.displayName, currentApp.version);
|
|
72
|
+
console.log(currentApp.id, currentApp.type, currentApp.securityMode);
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
## Arcane.applications.list()
|
|
76
|
+
|
|
77
|
+
### Overview
|
|
78
|
+
|
|
79
|
+
`Arcane.applications.list()` reads the installed, launchable application
|
|
80
|
+
catalog. It is available only to the bound `shell` or `terminal` application
|
|
81
|
+
with the `applications.read` grant on Core or Android. It performs a verified
|
|
82
|
+
catalog read and emits no event.
|
|
83
|
+
|
|
84
|
+
The result is a catalog wrapper, not a bare array. At most 256 unique
|
|
85
|
+
applications are returned, ordered by ascending `order`, then by display name
|
|
86
|
+
and id on Core. Android validates its generated order before returning it.
|
|
87
|
+
|
|
88
|
+
### Result
|
|
89
|
+
|
|
90
|
+
```javascript
|
|
91
|
+
const catalog = {
|
|
92
|
+
verified: true,
|
|
93
|
+
securityMode: 'unsigned-local-test',
|
|
94
|
+
publisherTrustSource: null,
|
|
95
|
+
revocationStatus: null,
|
|
96
|
+
applications: [
|
|
97
|
+
{
|
|
98
|
+
id: 'files',
|
|
99
|
+
displayName: 'Files',
|
|
100
|
+
description: 'Browse app-owned files.',
|
|
101
|
+
iconUrl: '/apps/files/icon.png',
|
|
102
|
+
version: '1.0.0',
|
|
103
|
+
order: 10,
|
|
104
|
+
verified: true
|
|
105
|
+
}
|
|
106
|
+
]
|
|
107
|
+
};
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
Each record has exactly `id`, `displayName`, `description`, `iconUrl`,
|
|
111
|
+
`version`, `order`, and `verified`. Nullable metadata is returned as `null`.
|
|
112
|
+
Every record's `verified` value equals the wrapper value. Publisher-verified
|
|
113
|
+
Core catalogs include bounded trust source and revocation evidence;
|
|
114
|
+
unsigned-development catalogs use `null` evidence. A non-debug Android package
|
|
115
|
+
can report `verified: false`, `securityMode: "unverified"`, and null evidence;
|
|
116
|
+
do not turn that development fact into a trust claim.
|
|
117
|
+
|
|
118
|
+
### Errors and recovery
|
|
119
|
+
|
|
120
|
+
`METHOD_NOT_ALLOWED` or `ANDROID_CAPABILITY_DENIED` means the app identity or
|
|
121
|
+
grant is not admitted. Catalog or package failures use
|
|
122
|
+
`APPLICATION_ADAPTER_UNAVAILABLE`, `APPLICATION_CATALOG_UNAVAILABLE`,
|
|
123
|
+
`APPLICATION_CATALOG_UNVERIFIED`, or `APPLICATION_CATALOG_INVALID`. Treat those
|
|
124
|
+
as installation-integrity failures and offer repair; do not display or launch a
|
|
125
|
+
partially accepted catalog.
|
|
126
|
+
|
|
127
|
+
### Example
|
|
128
|
+
|
|
129
|
+
```javascript
|
|
130
|
+
const catalog = await Arcane.applications.list();
|
|
131
|
+
|
|
132
|
+
if (!catalog.verified) {
|
|
133
|
+
console.warn('The host did not verify this development catalog.');
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
for (const application of catalog.applications) {
|
|
137
|
+
console.log(application.id, application.displayName, application.version);
|
|
138
|
+
}
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
## Arcane.applications.launch()
|
|
142
|
+
|
|
143
|
+
### Overview
|
|
144
|
+
|
|
145
|
+
`Arcane.applications.launch(id)` asks the host to dispatch one installed
|
|
146
|
+
application from the catalog returned by `applications.list()`. It is a
|
|
147
|
+
non-idempotent application-dispatch operation. The method is available only to
|
|
148
|
+
the bound `shell` or `terminal` application with `applications.launch` on Core
|
|
149
|
+
or Android.
|
|
150
|
+
|
|
151
|
+
Pass the unchanged canonical id from the current catalog. It must be a
|
|
152
|
+
lower-case hyphenated application id no longer than 64 characters; reserved
|
|
153
|
+
host identities such as Shell and Provisioner are not application launch
|
|
154
|
+
targets. Android additionally requires the generated APK to be installed and
|
|
155
|
+
launchable.
|
|
156
|
+
|
|
157
|
+
### Result and lifecycle
|
|
158
|
+
|
|
159
|
+
The exact result is `{ id, accepted: true }`. `accepted` means the host accepted
|
|
160
|
+
the dispatch request. It does not prove that the target rendered successfully,
|
|
161
|
+
became ready, or remained open. There is no portable public launch-completion
|
|
162
|
+
event, so do not wait for one or retry automatically after an ambiguous
|
|
163
|
+
transport failure.
|
|
164
|
+
|
|
165
|
+
### Errors and recovery
|
|
166
|
+
|
|
167
|
+
Use the current catalog again after `APPLICATION_NOT_FOUND`. Wait for an active
|
|
168
|
+
installation or application shutdown after `APPLICATION_INSTALL_BUSY` or
|
|
169
|
+
`APPLICATIONS_BUSY`. `INVALID_APPLICATION_ID` and
|
|
170
|
+
`INVALID_APPLICATION_REQUEST` indicate caller input. Adapter or dispatch
|
|
171
|
+
failures use `APPLICATION_ADAPTER_UNAVAILABLE`,
|
|
172
|
+
`APPLICATION_LAUNCH_FAILED`, or `APPLICATION_LAUNCH_REJECTED`; repair the host
|
|
173
|
+
if a catalog-listed application repeatedly fails.
|
|
174
|
+
|
|
175
|
+
### Example
|
|
176
|
+
|
|
177
|
+
```javascript
|
|
178
|
+
document.querySelector('#launch-files')?.addEventListener(
|
|
179
|
+
'click',
|
|
180
|
+
async function handleApplicationLaunch() {
|
|
181
|
+
const catalog = await Arcane.applications.list();
|
|
182
|
+
const filesApp = catalog.applications.find(function findFilesApp(application) {
|
|
183
|
+
return application.id === 'files';
|
|
184
|
+
});
|
|
185
|
+
if (!filesApp) return;
|
|
186
|
+
|
|
187
|
+
const launch = await Arcane.applications.launch(filesApp.id);
|
|
188
|
+
console.log('Dispatch accepted', launch.id, launch.accepted);
|
|
189
|
+
}
|
|
190
|
+
);
|
|
191
|
+
```
|
|
192
|
+
|
|
193
|
+
## Arcane.external.open()
|
|
194
|
+
|
|
195
|
+
### Overview
|
|
196
|
+
|
|
197
|
+
`Arcane.external.open(uri)` hands one validated `mailto:` URI to the operating
|
|
198
|
+
system's registered handler. It requires the `external.open` grant and an
|
|
199
|
+
admitted Core or Android host. The call is non-idempotent, emits no Arcane
|
|
200
|
+
event, and simulation rejects it instead of pretending to open another app.
|
|
201
|
+
|
|
202
|
+
The URI may be at most 4096 printable ASCII characters. It must have no leading
|
|
203
|
+
or trailing whitespace, raw spaces, fragment, backslash, malformed percent
|
|
204
|
+
escape, or raw or percent-encoded control character. Only `mailto:` is
|
|
205
|
+
accepted; percent-encode query values with `encodeURIComponent()`.
|
|
206
|
+
|
|
207
|
+
### Result and side effect
|
|
208
|
+
|
|
209
|
+
The exact result is `{ opened: true, uri }`, with the scheme canonicalized to
|
|
210
|
+
lower-case `mailto:`. `opened: true` means only that the host accepted the
|
|
211
|
+
operating-system handoff. It does not prove that a composer appeared or that a
|
|
212
|
+
message was sent. On Microsoft NT the host uses the system URI handler; Linux
|
|
213
|
+
requires `xdg-open`; Android launches an admitted intent handler.
|
|
214
|
+
|
|
215
|
+
### Errors and recovery
|
|
216
|
+
|
|
217
|
+
`EXTERNAL_SCHEME_NOT_ALLOWED` means the URI is not `mailto:`.
|
|
218
|
+
`EXTERNAL_OPEN_INVALID` identifies malformed input. `EXTERNAL_OPEN_SIMULATED`
|
|
219
|
+
and `EXTERNAL_OPEN_UNSUPPORTED` require a real host with a configured handler.
|
|
220
|
+
`EXTERNAL_OPEN_FAILED` means the OS did not accept the handoff. Do not retry
|
|
221
|
+
blindly after a timeout because the first handoff may already have occurred.
|
|
222
|
+
|
|
223
|
+
### Example
|
|
224
|
+
|
|
225
|
+
```javascript
|
|
226
|
+
document.querySelector('#open-support-email')?.addEventListener(
|
|
227
|
+
'click',
|
|
228
|
+
async function handleSupportEmailRequest() {
|
|
229
|
+
const subject = encodeURIComponent('Arcane support request');
|
|
230
|
+
const uri = `mailto:support@example.com?subject=${subject}`;
|
|
231
|
+
const result = await Arcane.external.open(uri);
|
|
232
|
+
console.log('Operating-system handoff accepted', result.opened);
|
|
233
|
+
}
|
|
234
|
+
);
|
|
235
|
+
```
|
|
236
|
+
|
|
237
|
+
## Arcane.mail.send()
|
|
238
|
+
|
|
239
|
+
### Overview
|
|
240
|
+
|
|
241
|
+
`Arcane.mail.send({report, reportKey})` sends one bounded report to the fixed
|
|
242
|
+
local Arcane mail gateway. It is admitted only to explicitly approved reporting
|
|
243
|
+
applications with `mail.send` on Microsoft NT or Linux Core. Android does not
|
|
244
|
+
project it, and simulation fails explicitly. Core performs no automatic retry.
|
|
245
|
+
|
|
246
|
+
`reportKey` is the caller's idempotency key: 8–128 characters from letters,
|
|
247
|
+
numbers, period, underscore, colon, and hyphen. Reuse the same key only when
|
|
248
|
+
retrying the same logical report after an uncertain local-gateway outcome.
|
|
249
|
+
|
|
250
|
+
### Input
|
|
251
|
+
|
|
252
|
+
`report` is an exact object with required `type`, `subject`, and `to`, plus
|
|
253
|
+
optional `text` and `html`; unknown fields are rejected. `type` is
|
|
254
|
+
`"crisis_detected"`, `"error"`, or `"report"`. The trimmed subject is 1–160
|
|
255
|
+
characters without controls. `to` contains at most 50 email-shaped addresses,
|
|
256
|
+
each at most 254 characters; it may be empty only for an `error` report. At
|
|
257
|
+
least one of `text` or `html` must contain non-whitespace content. The serialized
|
|
258
|
+
report may not exceed 786,432 UTF-8 bytes.
|
|
259
|
+
|
|
260
|
+
### Result
|
|
261
|
+
|
|
262
|
+
The exact result is
|
|
263
|
+
`{requestId, status, statusCode, sent, partial, uncertain}`. The combinations
|
|
264
|
+
are fixed:
|
|
265
|
+
|
|
266
|
+
| `status` | `statusCode` | Flags |
|
|
267
|
+
| --- | --- | --- |
|
|
268
|
+
| `accepted` | `202` | `sent: true`, `partial: false`, `uncertain: false` |
|
|
269
|
+
| `partially_accepted` | `207` | `sent: false`, `partial: true`, `uncertain: false` |
|
|
270
|
+
| `delivery_uncertain` | `207` | `sent: false`, `partial: false`, `uncertain: true` |
|
|
271
|
+
|
|
272
|
+
These are gateway acceptance states, not proof that every downstream recipient
|
|
273
|
+
received a message. The method has a 450-second bridge timeout around a
|
|
274
|
+
440-second absolute gateway deadline and emits no dedicated mail event.
|
|
275
|
+
|
|
276
|
+
### Errors and recovery
|
|
277
|
+
|
|
278
|
+
Malformed input is `METHOD_CONTRACT_INPUT_INVALID`. Simulation uses
|
|
279
|
+
`MAIL_SEND_SIMULATION_UNAVAILABLE`. Gateway connection, redirect, oversized or
|
|
280
|
+
invalid response, rejection, and timeout failures use the corresponding
|
|
281
|
+
`MAIL_GATEWAY_*`, `MAIL_SEND_REJECTED`, or `MAIL_SEND_TIMEOUT` code. Follow the
|
|
282
|
+
error's `resolution`; when a retry is appropriate, keep the same report and
|
|
283
|
+
`reportKey` so the gateway can deduplicate it. Never log report bodies or keys
|
|
284
|
+
that correlate sensitive reports.
|
|
285
|
+
|
|
286
|
+
### Example
|
|
287
|
+
|
|
288
|
+
```javascript
|
|
289
|
+
document.querySelector('#confirm-report-send')?.addEventListener(
|
|
290
|
+
'click',
|
|
291
|
+
async function handleConfirmedReportSend() {
|
|
292
|
+
const request = {
|
|
293
|
+
reportKey: `report:${crypto.randomUUID()}`,
|
|
294
|
+
report: {
|
|
295
|
+
type: 'report',
|
|
296
|
+
subject: 'Synthetic development report',
|
|
297
|
+
to: ['developer@example.com'],
|
|
298
|
+
text: 'This is synthetic test content.'
|
|
299
|
+
}
|
|
300
|
+
};
|
|
301
|
+
const result = await Arcane.mail.send(request);
|
|
302
|
+
console.log(result.requestId, result.status, result.statusCode);
|
|
303
|
+
}
|
|
304
|
+
);
|
|
305
|
+
```
|
|
306
|
+
|
|
307
|
+
## Arcane.capabilities.list()
|
|
308
|
+
|
|
309
|
+
### Overview
|
|
310
|
+
|
|
311
|
+
`Arcane.capabilities.list()` returns the effective authority of the current
|
|
312
|
+
Core-bound application session. It takes no parameters, requires no capability,
|
|
313
|
+
has no side effect, and emits no event. Use it to disable unavailable controls;
|
|
314
|
+
it does not grant authority or make a later mutation safe without its own
|
|
315
|
+
preconditions.
|
|
316
|
+
|
|
317
|
+
The direct method is Core-only in the current projection. Android callers with
|
|
318
|
+
`system.read` obtain the same nested capability snapshot from
|
|
319
|
+
`Arcane.platform.status().capabilities`; a direct Android
|
|
320
|
+
`capabilities.list` request is unsupported.
|
|
321
|
+
|
|
322
|
+
### Result
|
|
323
|
+
|
|
324
|
+
The exact result is `{ app, grants, methods }`. `app` is the same eight-field
|
|
325
|
+
descriptor returned by `Arcane.app.current()`. `grants` is the sorted list of
|
|
326
|
+
capability strings bound to the app. `methods` is the sorted list of exact RPC
|
|
327
|
+
method names admitted after capability, app-type, and app-id policy checks.
|
|
328
|
+
|
|
329
|
+
RPC names can differ from public JavaScript member names: for example,
|
|
330
|
+
`Arcane.applications.list()` is admitted as `"apps.list"`. Test the matching RPC
|
|
331
|
+
name and feature-detect the JavaScript member before enabling a control.
|
|
332
|
+
|
|
333
|
+
### Errors and recovery
|
|
334
|
+
|
|
335
|
+
`ARCANE_TRANSPORT_UNAVAILABLE` means there is no host. An Android call rejects
|
|
336
|
+
with `ANDROID_CAPABILITY_UNSUPPORTED`; use the platform-status snapshot there.
|
|
337
|
+
An unexpected or missing method in a Core result indicates a policy/package
|
|
338
|
+
mismatch—refresh the app after repair rather than treating a grant string alone
|
|
339
|
+
as permission.
|
|
340
|
+
|
|
341
|
+
### Example
|
|
342
|
+
|
|
343
|
+
```javascript
|
|
344
|
+
const access = await Arcane.capabilities.list();
|
|
345
|
+
const canLaunch = Boolean(
|
|
346
|
+
Arcane.applications?.launch
|
|
347
|
+
&& access.grants.includes('applications.launch')
|
|
348
|
+
&& access.methods.includes('apps.launch')
|
|
349
|
+
);
|
|
350
|
+
|
|
351
|
+
console.log(access.app.id, canLaunch);
|
|
352
|
+
```
|
|
353
|
+
|
|
354
|
+
## Arcane.terminal.start()
|
|
355
|
+
|
|
356
|
+
### Overview
|
|
357
|
+
|
|
358
|
+
`Arcane.terminal.start(options?)` starts one native terminal session owned by
|
|
359
|
+
the bound Arcane Terminal application. It is a high-risk, non-idempotent process
|
|
360
|
+
start. The host admits no more than eight concurrent sessions.
|
|
361
|
+
|
|
362
|
+
The method is available only when `terminal.start` is projected, the application
|
|
363
|
+
id is `terminal`, the `terminal.execute` grant is present, and `terminal.start`
|
|
364
|
+
appears in the admitted method list. A browser preview cannot start an operating
|
|
365
|
+
system process.
|
|
366
|
+
|
|
367
|
+
Subscribe to terminal events before calling `start()`. The host begins
|
|
368
|
+
forwarding process streams as part of startup, so an early output event may
|
|
369
|
+
arrive before application code receives the resolved session object. Buffer
|
|
370
|
+
events by `sessionId` until the start result identifies the desired session.
|
|
371
|
+
|
|
372
|
+
### Options
|
|
373
|
+
|
|
374
|
+
The optional `options` object has four normalized fields:
|
|
375
|
+
|
|
376
|
+
| Field | Type | Default | Contract |
|
|
377
|
+
| --- | --- | --- | --- |
|
|
378
|
+
| `shell` | `string` | `"auto"` | One of `auto`, `powershell`, `cmd`, `bash`, or `sh`, subject to host availability; at most 16 characters. |
|
|
379
|
+
| `cwd` | `string` | `""` | Existing accessible working directory, or the host default when empty; at most 4096 characters and subject to the host sandbox. |
|
|
380
|
+
| `columns` | safe integer | `120` | From 20 through 500. |
|
|
381
|
+
| `rows` | safe integer | `32` | From 5 through 200. |
|
|
382
|
+
|
|
383
|
+
The JavaScript wrapper sends only these four fields and converts the supplied
|
|
384
|
+
values to their documented string or number forms. Values outside the checked
|
|
385
|
+
bounds are rejected; do not rely on host clamping.
|
|
386
|
+
|
|
387
|
+
### Resolved session
|
|
388
|
+
|
|
389
|
+
The method resolves to:
|
|
390
|
+
|
|
391
|
+
```javascript
|
|
392
|
+
const session = {
|
|
393
|
+
id: 'term-example',
|
|
394
|
+
shell: 'powershell',
|
|
395
|
+
cwd: '<resolved working directory>',
|
|
396
|
+
title: 'PowerShell',
|
|
397
|
+
columns: 120,
|
|
398
|
+
rows: 32,
|
|
399
|
+
createdAt: '2026-08-15T12:00:00.000Z'
|
|
400
|
+
};
|
|
401
|
+
```
|
|
402
|
+
|
|
403
|
+
| Property | Type | Description |
|
|
404
|
+
| --- | --- | --- |
|
|
405
|
+
| `id` | `string` | Opaque session identifier matching the Arcane session-id contract and no longer than 128 characters. |
|
|
406
|
+
| `shell` | `string` | Resolved shell selected by the host. |
|
|
407
|
+
| `cwd` | `string` | Resolved working directory. |
|
|
408
|
+
| `title` | `string` | Host-provided display title, at most 80 characters. |
|
|
409
|
+
| `columns` | `number` | Accepted column count. |
|
|
410
|
+
| `rows` | `number` | Accepted row count. |
|
|
411
|
+
| `createdAt` | `string` | Host creation timestamp. |
|
|
412
|
+
|
|
413
|
+
### Events
|
|
414
|
+
|
|
415
|
+
After startup, observe these future-only events:
|
|
416
|
+
|
|
417
|
+
- `terminal.output` carries `{ sessionId, stream, data }` for stdout and stderr
|
|
418
|
+
chunks. Chunks are not lines and may contain terminal control sequences.
|
|
419
|
+
- `terminal.exit` carries `{ sessionId, exitCode, signal }` after the process
|
|
420
|
+
exits and the host retires the session.
|
|
421
|
+
- `terminal.error` carries `{ sessionId, message }` for asynchronous host stream
|
|
422
|
+
failures. The Android provider also uses it when a session exceeds its output
|
|
423
|
+
limit.
|
|
424
|
+
|
|
425
|
+
Store every returned unsubscribe function and call it during teardown. The
|
|
426
|
+
[Arcane event catalog](../../arcane-events.md) defines the complete event
|
|
427
|
+
payloads.
|
|
428
|
+
|
|
429
|
+
### Platform differences
|
|
430
|
+
|
|
431
|
+
On Microsoft NT Core hosts, `auto` resolves to PowerShell. `powershell`, `cmd`,
|
|
432
|
+
and an installed `bash` are selectable; `sh` is unavailable. On Linux Core
|
|
433
|
+
hosts, `auto` resolves to Bash, `sh` selects `/bin/sh`, `powershell` requires an
|
|
434
|
+
installed `pwsh`, and `cmd` is unavailable.
|
|
435
|
+
|
|
436
|
+
On Android, only `auto` and `sh` are accepted and both resolve to the
|
|
437
|
+
application-sandbox `/system/bin/sh`. The working directory must stay inside the
|
|
438
|
+
application's private files area. Android runs the process as the ordinary
|
|
439
|
+
Arcane Terminal app identity and stops a session after one MiB of emitted
|
|
440
|
+
output.
|
|
441
|
+
|
|
442
|
+
### Errors and recovery
|
|
443
|
+
|
|
444
|
+
| Code | Meaning and recovery |
|
|
445
|
+
| --- | --- |
|
|
446
|
+
| `METHOD_NOT_ALLOWED` | The bound application or grant is wrong. Open the admitted Arcane Terminal application; do not retry from another app. |
|
|
447
|
+
| `ARCANE_TRANSPORT_UNAVAILABLE` | Open Arcane Terminal through an installed or development Arcane host. |
|
|
448
|
+
| `METHOD_CONTRACT_INPUT_INVALID` or `TERMINAL_REQUEST_INVALID` | Use only the documented fields, value types, and bounds. |
|
|
449
|
+
| `TERMINAL_SESSION_LIMIT` | Close an existing session before retrying. |
|
|
450
|
+
| `TERMINAL_SHELL_INVALID` | Use one of the five documented shell names. |
|
|
451
|
+
| `TERMINAL_SHELL_UNAVAILABLE` | Select a shell supported and installed on the active platform. |
|
|
452
|
+
| `TERMINAL_CWD_INVALID` | Choose an existing accessible directory allowed by the current host sandbox. |
|
|
453
|
+
| `TERMINAL_START_FAILED` | Verify that the resolved shell executable is installed and can start, then retry. |
|
|
454
|
+
|
|
455
|
+
### Example
|
|
456
|
+
|
|
457
|
+
This complete example subscribes before startup, buffers early output, uses the
|
|
458
|
+
session methods, requests closure, observes process exit, and releases every
|
|
459
|
+
listener.
|
|
460
|
+
|
|
461
|
+
```javascript
|
|
462
|
+
const arcane = globalThis.Arcane;
|
|
463
|
+
const terminal = arcane?.terminal;
|
|
464
|
+
const events = arcane?.events;
|
|
465
|
+
|
|
466
|
+
if (!terminal?.start || !events?.on || !arcane?.capabilities?.list) {
|
|
467
|
+
throw new Error('Open Arcane Terminal through an admitted Arcane host.');
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
const access = await arcane.capabilities.list();
|
|
471
|
+
const requiredMethods = [
|
|
472
|
+
'terminal.start',
|
|
473
|
+
'terminal.list',
|
|
474
|
+
'terminal.write',
|
|
475
|
+
'terminal.resize',
|
|
476
|
+
'terminal.close'
|
|
477
|
+
];
|
|
478
|
+
|
|
479
|
+
if (
|
|
480
|
+
access.app?.id !== 'terminal'
|
|
481
|
+
|| !access.grants.includes('terminal.execute')
|
|
482
|
+
|| !requiredMethods.every(function isRequiredTerminalMethodAdmitted(method) {
|
|
483
|
+
return access.methods.includes(method);
|
|
484
|
+
})
|
|
485
|
+
) {
|
|
486
|
+
throw new Error('This application is not admitted for terminal execution.');
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
const bufferedOutput = new Map();
|
|
490
|
+
const observedExits = new Map();
|
|
491
|
+
const exitWaiters = new Map();
|
|
492
|
+
const maxBufferedChunksPerSession = 128;
|
|
493
|
+
let session = null;
|
|
494
|
+
let activeSessionId = null;
|
|
495
|
+
|
|
496
|
+
function render({stream, data}) {
|
|
497
|
+
const write = stream === 'stderr' ? console.error : console.log;
|
|
498
|
+
write(data);
|
|
499
|
+
}
|
|
500
|
+
|
|
501
|
+
const offOutput = events.on('terminal.output', function handleTerminalOutput(payload) {
|
|
502
|
+
if (payload.sessionId === activeSessionId) {
|
|
503
|
+
render(payload);
|
|
504
|
+
return;
|
|
505
|
+
}
|
|
506
|
+
const pending = bufferedOutput.get(payload.sessionId) ?? [];
|
|
507
|
+
pending.push(payload);
|
|
508
|
+
// Bound pre-identification buffering; surface truncation in a real UI.
|
|
509
|
+
if (pending.length > maxBufferedChunksPerSession) {
|
|
510
|
+
pending.shift();
|
|
511
|
+
}
|
|
512
|
+
bufferedOutput.set(payload.sessionId, pending);
|
|
513
|
+
});
|
|
514
|
+
|
|
515
|
+
const offExit = events.on('terminal.exit', function handleTerminalExit(payload) {
|
|
516
|
+
observedExits.set(payload.sessionId, payload);
|
|
517
|
+
const waiter = exitWaiters.get(payload.sessionId);
|
|
518
|
+
if (waiter) {
|
|
519
|
+
clearTimeout(waiter.timer);
|
|
520
|
+
exitWaiters.delete(payload.sessionId);
|
|
521
|
+
waiter.resolve(payload);
|
|
522
|
+
}
|
|
523
|
+
});
|
|
524
|
+
|
|
525
|
+
const offError = events.on('terminal.error', function handleTerminalError(payload) {
|
|
526
|
+
console.error(`Terminal ${payload.sessionId}: ${payload.message}`);
|
|
527
|
+
});
|
|
528
|
+
|
|
529
|
+
function waitForExit(sessionId, timeoutMs = 5000) {
|
|
530
|
+
if (observedExits.has(sessionId)) {
|
|
531
|
+
return Promise.resolve(observedExits.get(sessionId));
|
|
532
|
+
}
|
|
533
|
+
return new Promise(function createExitWait(resolve, reject) {
|
|
534
|
+
const timer = setTimeout(function rejectTimedOutExitWait() {
|
|
535
|
+
exitWaiters.delete(sessionId);
|
|
536
|
+
reject(new Error(`Timed out waiting for ${sessionId} to exit.`));
|
|
537
|
+
}, timeoutMs);
|
|
538
|
+
exitWaiters.set(sessionId, {resolve, timer});
|
|
539
|
+
});
|
|
540
|
+
}
|
|
541
|
+
|
|
542
|
+
try {
|
|
543
|
+
session = await terminal.start({
|
|
544
|
+
shell: 'auto',
|
|
545
|
+
cwd: '',
|
|
546
|
+
columns: 120,
|
|
547
|
+
rows: 32
|
|
548
|
+
});
|
|
549
|
+
activeSessionId = session.id;
|
|
550
|
+
|
|
551
|
+
for (const payload of bufferedOutput.get(session.id) ?? []) {
|
|
552
|
+
render(payload);
|
|
553
|
+
}
|
|
554
|
+
bufferedOutput.delete(session.id);
|
|
555
|
+
|
|
556
|
+
await terminal.resize(session.id, 100, 30);
|
|
557
|
+
|
|
558
|
+
const {sessions} = await terminal.list();
|
|
559
|
+
console.log('Owned sessions', sessions);
|
|
560
|
+
|
|
561
|
+
const lineEnding = ['powershell', 'cmd'].includes(session.shell)
|
|
562
|
+
? '\r\n'
|
|
563
|
+
: '\n';
|
|
564
|
+
const writeResult = await terminal.write(
|
|
565
|
+
session.id,
|
|
566
|
+
`echo Arcane terminal ready${lineEnding}`
|
|
567
|
+
);
|
|
568
|
+
console.log('Accepted input bytes', writeResult.bytes);
|
|
569
|
+
|
|
570
|
+
const exitPromise = waitForExit(session.id);
|
|
571
|
+
const closeResult = await terminal.close(session.id);
|
|
572
|
+
console.log('Close request accepted', closeResult.accepted);
|
|
573
|
+
|
|
574
|
+
const exit = await exitPromise;
|
|
575
|
+
console.log('Process exit', exit.exitCode, exit.signal);
|
|
576
|
+
} catch (error) {
|
|
577
|
+
if (error instanceof arcane.Error) {
|
|
578
|
+
console.error(error.code, error.message, error.resolution);
|
|
579
|
+
} else {
|
|
580
|
+
throw error;
|
|
581
|
+
}
|
|
582
|
+
} finally {
|
|
583
|
+
if (session && !observedExits.has(session.id)) {
|
|
584
|
+
await terminal.close(session.id).catch(function ignoreTerminalCloseFailure() {});
|
|
585
|
+
}
|
|
586
|
+
offOutput();
|
|
587
|
+
offExit();
|
|
588
|
+
offError();
|
|
589
|
+
for (const waiter of exitWaiters.values()) {
|
|
590
|
+
clearTimeout(waiter.timer);
|
|
591
|
+
}
|
|
592
|
+
exitWaiters.clear();
|
|
593
|
+
}
|
|
594
|
+
```
|
|
595
|
+
|
|
596
|
+
## Arcane.terminal.list()
|
|
597
|
+
|
|
598
|
+
### Overview
|
|
599
|
+
|
|
600
|
+
`Arcane.terminal.list()` returns the current terminal sessions owned by the
|
|
601
|
+
bound application. It is a repeatable read, takes no parameters, and does not
|
|
602
|
+
list sessions owned by another application or user boundary.
|
|
603
|
+
|
|
604
|
+
The resolved value is a wrapper object, not the session array itself:
|
|
605
|
+
|
|
606
|
+
```javascript
|
|
607
|
+
const {sessions} = await Arcane.terminal.list();
|
|
608
|
+
```
|
|
609
|
+
|
|
610
|
+
At most eight entries are returned. A host may remove an exited or closed
|
|
611
|
+
session promptly, so use terminal events for lifecycle observation rather than
|
|
612
|
+
using repeated list calls as a substitute for event delivery.
|
|
613
|
+
|
|
614
|
+
### Session inventory
|
|
615
|
+
|
|
616
|
+
The exact resolved shape is:
|
|
617
|
+
|
|
618
|
+
```javascript
|
|
619
|
+
const result = {
|
|
620
|
+
sessions: [
|
|
621
|
+
{
|
|
622
|
+
id: 'term-example',
|
|
623
|
+
shell: 'powershell',
|
|
624
|
+
cwd: '<resolved working directory>',
|
|
625
|
+
columns: 120,
|
|
626
|
+
rows: 32,
|
|
627
|
+
createdAt: '2026-08-15T12:00:00.000Z',
|
|
628
|
+
state: 'running'
|
|
629
|
+
}
|
|
630
|
+
]
|
|
631
|
+
};
|
|
632
|
+
```
|
|
633
|
+
|
|
634
|
+
| Property | Type | Description |
|
|
635
|
+
| --- | --- | --- |
|
|
636
|
+
| `id` | `string` | Opaque app-owned session id. |
|
|
637
|
+
| `shell` | `string` | Resolved shell name. |
|
|
638
|
+
| `cwd` | `string` | Resolved working directory. |
|
|
639
|
+
| `columns` | `number` | Current bounded column count. |
|
|
640
|
+
| `rows` | `number` | Current bounded row count. |
|
|
641
|
+
| `createdAt` | `string` | Host creation timestamp. |
|
|
642
|
+
| `state` | `string` | Current host-reported state while the session remains in the inventory: `starting`, `running`, `exited`, or `closed`. |
|
|
643
|
+
|
|
644
|
+
`list()` deliberately does not return the `title` property included in the
|
|
645
|
+
`start()` result.
|
|
646
|
+
|
|
647
|
+
### Errors and recovery
|
|
648
|
+
|
|
649
|
+
`METHOD_NOT_ALLOWED` means the application is not admitted for
|
|
650
|
+
`terminal.execute`. `ARCANE_TRANSPORT_UNAVAILABLE` means no callable host is
|
|
651
|
+
connected. A host result that violates the exact wrapper or session shape is
|
|
652
|
+
rejected as `METHOD_CONTRACT_OUTPUT_INVALID`; treat that as a host/package
|
|
653
|
+
integrity failure rather than trying to reinterpret the result.
|
|
654
|
+
|
|
655
|
+
### Example
|
|
656
|
+
|
|
657
|
+
```javascript
|
|
658
|
+
const terminal = globalThis.Arcane?.terminal;
|
|
659
|
+
|
|
660
|
+
if (!terminal?.list) {
|
|
661
|
+
throw new Error('Terminal session inventory is unavailable.');
|
|
662
|
+
}
|
|
663
|
+
|
|
664
|
+
try {
|
|
665
|
+
const {sessions} = await terminal.list();
|
|
666
|
+
|
|
667
|
+
for (const session of sessions) {
|
|
668
|
+
console.log(
|
|
669
|
+
session.id,
|
|
670
|
+
session.shell,
|
|
671
|
+
session.state,
|
|
672
|
+
`${session.columns}x${session.rows}`,
|
|
673
|
+
session.cwd
|
|
674
|
+
);
|
|
675
|
+
}
|
|
676
|
+
} catch (error) {
|
|
677
|
+
if (error instanceof Arcane.Error) {
|
|
678
|
+
console.error(error.code, error.message, error.resolution);
|
|
679
|
+
} else {
|
|
680
|
+
throw error;
|
|
681
|
+
}
|
|
682
|
+
}
|
|
683
|
+
```
|
|
684
|
+
|
|
685
|
+
## Arcane.terminal.write()
|
|
686
|
+
|
|
687
|
+
### Overview
|
|
688
|
+
|
|
689
|
+
`Arcane.terminal.write(sessionId, data)` writes one nonempty UTF-8 input chunk
|
|
690
|
+
to a running app-owned session. The method is non-idempotent: after an ambiguous
|
|
691
|
+
timeout or transport failure, do not retry blindly because the first input may
|
|
692
|
+
already have reached the process.
|
|
693
|
+
|
|
694
|
+
The method does not append a line ending. Use `"\r\n"` for PowerShell or
|
|
695
|
+
Command Prompt and `"\n"` for POSIX shells when the target shell should submit
|
|
696
|
+
a command. It also does not return process output or command completion;
|
|
697
|
+
subscribe to `terminal.output` and `terminal.exit` before writing.
|
|
698
|
+
|
|
699
|
+
Output events contain stream chunks, not lines. Preserve per-session arrival
|
|
700
|
+
order and expect a chunk to contain partial text, multiple lines, or terminal
|
|
701
|
+
control sequences.
|
|
702
|
+
|
|
703
|
+
### Input
|
|
704
|
+
|
|
705
|
+
| Parameter | Type | Contract |
|
|
706
|
+
| --- | --- | --- |
|
|
707
|
+
| `sessionId` | `string` | Opaque id returned by `start()` or `list()`, from 1 through 128 characters and matching the Arcane session-id pattern. |
|
|
708
|
+
| `data` | `string` | Nonempty UTF-8 input from 1 through 65,536 bytes. |
|
|
709
|
+
|
|
710
|
+
The JavaScript wrapper converts `sessionId` and `data` to strings. An empty value
|
|
711
|
+
is still invalid, and the limit is measured in UTF-8 bytes rather than JavaScript
|
|
712
|
+
characters.
|
|
713
|
+
|
|
714
|
+
### Acceptance result
|
|
715
|
+
|
|
716
|
+
The method resolves to:
|
|
717
|
+
|
|
718
|
+
```javascript
|
|
719
|
+
const result = {
|
|
720
|
+
sessionId: 'term-example',
|
|
721
|
+
accepted: true,
|
|
722
|
+
bytes: 21
|
|
723
|
+
};
|
|
724
|
+
```
|
|
725
|
+
|
|
726
|
+
`bytes` is the accepted UTF-8 byte count, from 1 through 65,536. `accepted`
|
|
727
|
+
means the host accepted the input for the session; it does not mean the shell
|
|
728
|
+
finished a command.
|
|
729
|
+
|
|
730
|
+
### Errors and recovery
|
|
731
|
+
|
|
732
|
+
| Code | Meaning and recovery |
|
|
733
|
+
| --- | --- |
|
|
734
|
+
| `METHOD_CONTRACT_INPUT_INVALID` or `TERMINAL_DATA_INVALID` | Send a nonempty chunk no larger than 64 KiB. Split larger input deliberately. |
|
|
735
|
+
| `TERMINAL_SESSION_INVALID` | Use an unchanged id returned by the API. |
|
|
736
|
+
| `TERMINAL_SESSION_NOT_FOUND` | The session has exited or was closed. Refresh with `list()` or start another session. |
|
|
737
|
+
| `TERMINAL_INPUT_CLOSED` | The process no longer accepts stdin. Start a new session instead of retrying. |
|
|
738
|
+
| `METHOD_NOT_ALLOWED` | The current application lacks terminal admission. |
|
|
739
|
+
|
|
740
|
+
### Example
|
|
741
|
+
|
|
742
|
+
```javascript
|
|
743
|
+
const terminal = globalThis.Arcane?.terminal;
|
|
744
|
+
const events = globalThis.Arcane?.events;
|
|
745
|
+
|
|
746
|
+
if (!terminal?.write || !terminal?.list || !events?.on) {
|
|
747
|
+
throw new Error('Native terminal input is unavailable.');
|
|
748
|
+
}
|
|
749
|
+
|
|
750
|
+
const {sessions} = await terminal.list();
|
|
751
|
+
const session = sessions[0];
|
|
752
|
+
|
|
753
|
+
if (!session) {
|
|
754
|
+
throw new Error('Start a terminal session before writing input.');
|
|
755
|
+
}
|
|
756
|
+
|
|
757
|
+
const offOutput = events.on('terminal.output', function handleTerminalOutput(payload) {
|
|
758
|
+
if (payload.sessionId === session.id) {
|
|
759
|
+
console.log(payload.stream, payload.data);
|
|
760
|
+
}
|
|
761
|
+
});
|
|
762
|
+
|
|
763
|
+
try {
|
|
764
|
+
const lineEnding = ['powershell', 'cmd'].includes(session.shell)
|
|
765
|
+
? '\r\n'
|
|
766
|
+
: '\n';
|
|
767
|
+
const result = await terminal.write(
|
|
768
|
+
session.id,
|
|
769
|
+
`echo Input was accepted${lineEnding}`
|
|
770
|
+
);
|
|
771
|
+
console.log(`Accepted ${result.bytes} UTF-8 bytes.`);
|
|
772
|
+
} catch (error) {
|
|
773
|
+
if (error instanceof Arcane.Error) {
|
|
774
|
+
console.error(error.code, error.message, error.resolution);
|
|
775
|
+
} else {
|
|
776
|
+
throw error;
|
|
777
|
+
}
|
|
778
|
+
} finally {
|
|
779
|
+
offOutput();
|
|
780
|
+
}
|
|
781
|
+
```
|
|
782
|
+
|
|
783
|
+
## Arcane.terminal.resize()
|
|
784
|
+
|
|
785
|
+
### Overview
|
|
786
|
+
|
|
787
|
+
`Arcane.terminal.resize(sessionId, columns, rows)` updates the bounded dimensions
|
|
788
|
+
recorded for a running app-owned session. The current host contract reports this
|
|
789
|
+
as an emulated resize. It updates Arcane's terminal-session dimensions but does
|
|
790
|
+
not promise a native pseudoterminal resize or emit a resize event.
|
|
791
|
+
|
|
792
|
+
### Dimensions
|
|
793
|
+
|
|
794
|
+
| Parameter | Type | Contract |
|
|
795
|
+
| --- | --- | --- |
|
|
796
|
+
| `sessionId` | `string` | Opaque id returned by `start()` or `list()`, no longer than 128 characters. |
|
|
797
|
+
| `columns` | safe integer | From 20 through 500. |
|
|
798
|
+
| `rows` | safe integer | From 5 through 200. |
|
|
799
|
+
|
|
800
|
+
The wrapper converts both dimensions with `Number()`. Fractions, non-finite
|
|
801
|
+
values, and out-of-range values do not satisfy the checked method contract.
|
|
802
|
+
|
|
803
|
+
### Acceptance result
|
|
804
|
+
|
|
805
|
+
The method resolves to this exact object:
|
|
806
|
+
|
|
807
|
+
```javascript
|
|
808
|
+
const result = {
|
|
809
|
+
sessionId: 'term-example',
|
|
810
|
+
columns: 100,
|
|
811
|
+
rows: 30,
|
|
812
|
+
accepted: true,
|
|
813
|
+
emulated: true
|
|
814
|
+
};
|
|
815
|
+
```
|
|
816
|
+
|
|
817
|
+
The returned dimensions are the accepted values. `emulated: true` distinguishes
|
|
818
|
+
this session metadata update from a guarantee that the operating system resized
|
|
819
|
+
a native pseudoterminal.
|
|
820
|
+
|
|
821
|
+
### Errors and recovery
|
|
822
|
+
|
|
823
|
+
Invalid dimensions are rejected as `METHOD_CONTRACT_INPUT_INVALID`. An invalid
|
|
824
|
+
or retired id produces `TERMINAL_SESSION_INVALID` or
|
|
825
|
+
`TERMINAL_SESSION_NOT_FOUND`. Use `list()` to refresh current app-owned sessions;
|
|
826
|
+
do not reuse an id after exit.
|
|
827
|
+
|
|
828
|
+
### Example
|
|
829
|
+
|
|
830
|
+
```javascript
|
|
831
|
+
const terminal = globalThis.Arcane?.terminal;
|
|
832
|
+
|
|
833
|
+
if (!terminal?.resize || !terminal?.list) {
|
|
834
|
+
throw new Error('Terminal resize is unavailable.');
|
|
835
|
+
}
|
|
836
|
+
|
|
837
|
+
const {sessions} = await terminal.list();
|
|
838
|
+
const session = sessions[0];
|
|
839
|
+
|
|
840
|
+
if (!session) {
|
|
841
|
+
throw new Error('Start a terminal session before resizing it.');
|
|
842
|
+
}
|
|
843
|
+
|
|
844
|
+
try {
|
|
845
|
+
const result = await terminal.resize(session.id, 100, 30);
|
|
846
|
+
console.log(
|
|
847
|
+
`Recorded ${result.columns}x${result.rows}`,
|
|
848
|
+
`emulated=${result.emulated}`
|
|
849
|
+
);
|
|
850
|
+
} catch (error) {
|
|
851
|
+
if (error instanceof Arcane.Error) {
|
|
852
|
+
console.error(error.code, error.message, error.resolution);
|
|
853
|
+
} else {
|
|
854
|
+
throw error;
|
|
855
|
+
}
|
|
856
|
+
}
|
|
857
|
+
```
|
|
858
|
+
|
|
859
|
+
## Arcane.terminal.signal()
|
|
860
|
+
|
|
861
|
+
### Overview
|
|
862
|
+
|
|
863
|
+
`Arcane.terminal.signal(sessionId, signal = "interrupt")` sends one supported
|
|
864
|
+
control request to a running app-owned session. It is non-idempotent process
|
|
865
|
+
control. Subscribe to `terminal.exit` before signaling when the application
|
|
866
|
+
needs to observe whether the process exits.
|
|
867
|
+
|
|
868
|
+
### Signal request
|
|
869
|
+
|
|
870
|
+
| Parameter | Type | Default | Contract |
|
|
871
|
+
| --- | --- | --- | --- |
|
|
872
|
+
| `sessionId` | `string` | None | Opaque id returned by `start()` or `list()`, no longer than 128 characters. |
|
|
873
|
+
| `signal` | `string` | `"interrupt"` | Either `interrupt` or `terminate`; no other signal name is supported. |
|
|
874
|
+
|
|
875
|
+
On desktop Core hosts, `interrupt` maps to the host's `SIGINT` process-control
|
|
876
|
+
request and `terminate` maps to `SIGTERM`. Android applies the supported request
|
|
877
|
+
through its sandbox process-destruction boundary; applications must not depend
|
|
878
|
+
on Unix signal details there.
|
|
879
|
+
|
|
880
|
+
### Acceptance result
|
|
881
|
+
|
|
882
|
+
The method resolves to:
|
|
883
|
+
|
|
884
|
+
```javascript
|
|
885
|
+
const result = {
|
|
886
|
+
sessionId: 'term-example',
|
|
887
|
+
signal: 'interrupt',
|
|
888
|
+
accepted: true
|
|
889
|
+
};
|
|
890
|
+
```
|
|
891
|
+
|
|
892
|
+
`accepted` is a boolean and may be `false`. A true value means the host accepted
|
|
893
|
+
the control request, not that the process exited or used a particular exit code.
|
|
894
|
+
Observe `terminal.exit` for the final outcome.
|
|
895
|
+
|
|
896
|
+
### Errors and recovery
|
|
897
|
+
|
|
898
|
+
| Code | Meaning and recovery |
|
|
899
|
+
| --- | --- |
|
|
900
|
+
| `METHOD_CONTRACT_INPUT_INVALID` or `TERMINAL_SIGNAL_INVALID` | Use only `interrupt` or `terminate`. |
|
|
901
|
+
| `TERMINAL_SESSION_INVALID` | Use an unchanged API-returned session id. |
|
|
902
|
+
| `TERMINAL_SESSION_NOT_FOUND` | The process is no longer running; refresh with `list()` or start another session. |
|
|
903
|
+
| `METHOD_NOT_ALLOWED` | The current application lacks `terminal.execute` admission. |
|
|
904
|
+
|
|
905
|
+
Do not retry a signal automatically after an ambiguous transport failure; the
|
|
906
|
+
first request may already have affected the process.
|
|
907
|
+
|
|
908
|
+
### Example
|
|
909
|
+
|
|
910
|
+
```javascript
|
|
911
|
+
const terminal = globalThis.Arcane?.terminal;
|
|
912
|
+
const events = globalThis.Arcane?.events;
|
|
913
|
+
|
|
914
|
+
if (!terminal?.signal || !terminal?.list || !events?.on) {
|
|
915
|
+
throw new Error('Terminal process control is unavailable.');
|
|
916
|
+
}
|
|
917
|
+
|
|
918
|
+
const {sessions} = await terminal.list();
|
|
919
|
+
const session = sessions[0];
|
|
920
|
+
|
|
921
|
+
if (!session) {
|
|
922
|
+
throw new Error('Start a terminal session before signaling it.');
|
|
923
|
+
}
|
|
924
|
+
|
|
925
|
+
let offExit = function ignoreExitUnsubscribe() {};
|
|
926
|
+
|
|
927
|
+
const exited = new Promise(function waitForTerminalExit(resolve) {
|
|
928
|
+
offExit = events.on('terminal.exit', function handleTerminalExit(payload) {
|
|
929
|
+
if (payload.sessionId === session.id) {
|
|
930
|
+
offExit();
|
|
931
|
+
resolve(payload);
|
|
932
|
+
}
|
|
933
|
+
});
|
|
934
|
+
});
|
|
935
|
+
|
|
936
|
+
try {
|
|
937
|
+
const result = await terminal.signal(session.id, 'interrupt');
|
|
938
|
+
console.log('Interrupt request accepted', result.accepted);
|
|
939
|
+
|
|
940
|
+
if (result.accepted) {
|
|
941
|
+
const exit = await Promise.race([
|
|
942
|
+
exited,
|
|
943
|
+
new Promise(function waitForExitTimeout(resolve) {
|
|
944
|
+
setTimeout(function resolveExitTimeout() {
|
|
945
|
+
resolve(null);
|
|
946
|
+
}, 2000);
|
|
947
|
+
})
|
|
948
|
+
]);
|
|
949
|
+
console.log(exit ? 'Session exited' : 'Session remains available');
|
|
950
|
+
}
|
|
951
|
+
} catch (error) {
|
|
952
|
+
if (error instanceof Arcane.Error) {
|
|
953
|
+
console.error(error.code, error.message, error.resolution);
|
|
954
|
+
} else {
|
|
955
|
+
throw error;
|
|
956
|
+
}
|
|
957
|
+
} finally {
|
|
958
|
+
offExit();
|
|
959
|
+
}
|
|
960
|
+
```
|
|
961
|
+
|
|
962
|
+
## Arcane.terminal.close()
|
|
963
|
+
|
|
964
|
+
### Overview
|
|
965
|
+
|
|
966
|
+
`Arcane.terminal.close(sessionId)` asks the host to close one app-owned terminal
|
|
967
|
+
session. It is non-idempotent process control. The host may close input and then
|
|
968
|
+
terminate the process according to its platform policy.
|
|
969
|
+
|
|
970
|
+
The resolved result acknowledges the close request; it is not the process exit
|
|
971
|
+
record. Subscribe to `terminal.exit` before calling `close()` and use that event
|
|
972
|
+
as the final lifecycle observation. Calling `close()` again after the host
|
|
973
|
+
retires the session can reject with `TERMINAL_SESSION_NOT_FOUND`.
|
|
974
|
+
|
|
975
|
+
### Session identifier
|
|
976
|
+
|
|
977
|
+
| Parameter | Type | Contract |
|
|
978
|
+
| --- | --- | --- |
|
|
979
|
+
| `sessionId` | `string` | Opaque id returned by `start()` or `list()`, from 1 through 128 characters and matching the session-id contract. |
|
|
980
|
+
|
|
981
|
+
### Acceptance result
|
|
982
|
+
|
|
983
|
+
The method resolves to:
|
|
984
|
+
|
|
985
|
+
```javascript
|
|
986
|
+
const result = {
|
|
987
|
+
sessionId: 'term-example',
|
|
988
|
+
accepted: true
|
|
989
|
+
};
|
|
990
|
+
```
|
|
991
|
+
|
|
992
|
+
`accepted: true` means that the host accepted the close request. It does not
|
|
993
|
+
mean the process has exited, that an exit code is already available, or that all
|
|
994
|
+
earlier output chunks have been rendered.
|
|
995
|
+
|
|
996
|
+
### Errors and recovery
|
|
997
|
+
|
|
998
|
+
`TERMINAL_SESSION_INVALID` means the identifier does not satisfy the public
|
|
999
|
+
session-id contract. `TERMINAL_SESSION_NOT_FOUND` means the session has already
|
|
1000
|
+
exited or been retired. Treat the latter as stale local state and refresh the
|
|
1001
|
+
owned inventory instead of repeatedly closing the same id.
|
|
1002
|
+
|
|
1003
|
+
`METHOD_NOT_ALLOWED` and `ARCANE_TRANSPORT_UNAVAILABLE` indicate an availability
|
|
1004
|
+
or host problem, not a session problem. Reopen the admitted Arcane Terminal host
|
|
1005
|
+
rather than retrying the close call in a browser preview.
|
|
1006
|
+
|
|
1007
|
+
### Example
|
|
1008
|
+
|
|
1009
|
+
```javascript
|
|
1010
|
+
const terminal = globalThis.Arcane?.terminal;
|
|
1011
|
+
const events = globalThis.Arcane?.events;
|
|
1012
|
+
|
|
1013
|
+
if (!terminal?.close || !terminal?.list || !events?.on) {
|
|
1014
|
+
throw new Error('Terminal session closure is unavailable.');
|
|
1015
|
+
}
|
|
1016
|
+
|
|
1017
|
+
const {sessions} = await terminal.list();
|
|
1018
|
+
const session = sessions[0];
|
|
1019
|
+
|
|
1020
|
+
if (!session) {
|
|
1021
|
+
throw new Error('Start a terminal session before closing it.');
|
|
1022
|
+
}
|
|
1023
|
+
|
|
1024
|
+
let offExit = function ignoreExitUnsubscribe() {};
|
|
1025
|
+
|
|
1026
|
+
const exited = new Promise(function waitForTerminalExit(resolve) {
|
|
1027
|
+
offExit = events.on('terminal.exit', function handleTerminalExit(payload) {
|
|
1028
|
+
if (payload.sessionId === session.id) {
|
|
1029
|
+
offExit();
|
|
1030
|
+
resolve(payload);
|
|
1031
|
+
}
|
|
1032
|
+
});
|
|
1033
|
+
});
|
|
1034
|
+
|
|
1035
|
+
try {
|
|
1036
|
+
const result = await terminal.close(session.id);
|
|
1037
|
+
console.log('Close request accepted', result.accepted);
|
|
1038
|
+
|
|
1039
|
+
const exit = await Promise.race([
|
|
1040
|
+
exited,
|
|
1041
|
+
new Promise(function waitForExitTimeout(_resolve, reject) {
|
|
1042
|
+
setTimeout(function rejectExitTimeout() {
|
|
1043
|
+
reject(new Error('Timed out waiting for terminal.exit.'));
|
|
1044
|
+
}, 5000);
|
|
1045
|
+
})
|
|
1046
|
+
]);
|
|
1047
|
+
console.log('Process exited', exit.exitCode, exit.signal);
|
|
1048
|
+
} catch (error) {
|
|
1049
|
+
if (error instanceof Arcane.Error) {
|
|
1050
|
+
console.error(error.code, error.message, error.resolution);
|
|
1051
|
+
} else {
|
|
1052
|
+
console.error(error);
|
|
1053
|
+
}
|
|
1054
|
+
} finally {
|
|
1055
|
+
offExit();
|
|
1056
|
+
}
|
|
1057
|
+
```
|