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.
Files changed (248) hide show
  1. package/CHANGELOG.md +154 -0
  2. package/COMMERCIAL-LICENSE.md +13 -0
  3. package/LICENSE +661 -0
  4. package/NOTICE +70 -0
  5. package/README.md +448 -0
  6. package/bin/arcane-test.mjs +741 -0
  7. package/bin/arcane.mjs +5 -0
  8. package/docs/architecture.md +234 -0
  9. package/docs/compatibility.md +36 -0
  10. package/docs/event-manager.md +166 -0
  11. package/docs/platform-targets.md +108 -0
  12. package/docs/publishing.md +203 -0
  13. package/docs/reference/README.md +117 -0
  14. package/docs/reference/arcane-ollama.md +288 -0
  15. package/docs/reference/availability-and-normalization.md +123 -0
  16. package/docs/reference/behavioral-testing.md +86 -0
  17. package/docs/reference/cli.md +569 -0
  18. package/docs/reference/core/README.md +62 -0
  19. package/docs/reference/core/arcane-ai-contracts.md +873 -0
  20. package/docs/reference/core/arcane-api.md +601 -0
  21. package/docs/reference/core/arcane-entities.md +65 -0
  22. package/docs/reference/core/arcane-events.md +134 -0
  23. package/docs/reference/core/ollama-module.md +181 -0
  24. package/docs/reference/core/reference/arcane-api/ai-and-ollama.md +1909 -0
  25. package/docs/reference/core/reference/arcane-api/applications-terminal-capabilities.md +1057 -0
  26. package/docs/reference/core/reference/arcane-api/core-and-events.md +320 -0
  27. package/docs/reference/core/reference/arcane-api/filesystem-storage-preferences-appearance.md +610 -0
  28. package/docs/reference/core/reference/arcane-api/namespaces.md +1157 -0
  29. package/docs/reference/core/reference/arcane-api/platform-installation-users-system.md +1423 -0
  30. package/docs/reference/core/reference/arcane-api/session-provisioning-diagnostics-development.md +315 -0
  31. package/docs/reference/event-manager.md +957 -0
  32. package/docs/reference/inventory/package-api.json +2632 -0
  33. package/docs/reference/inventory/runtime-components.json +934 -0
  34. package/docs/reference/inventory/runtime-entities.json +26 -0
  35. package/docs/reference/inventory/runtime-modules.json +1249 -0
  36. package/docs/reference/protocols.md +242 -0
  37. package/docs/reference/runtime-components.md +1098 -0
  38. package/docs/reference/runtime-entities.md +303 -0
  39. package/docs/reference/runtime-modules.md +2010 -0
  40. package/docs/reference/sdk-api.md +4901 -0
  41. package/docs/roadmap.md +79 -0
  42. package/docs/work-amplification.md +124 -0
  43. package/node_modules/event-pubsub/CHANGELOG.md +55 -0
  44. package/node_modules/event-pubsub/MIGRATION.md +70 -0
  45. package/node_modules/event-pubsub/README.md +363 -0
  46. package/node_modules/event-pubsub/SECURITY.md +37 -0
  47. package/node_modules/event-pubsub/index.js +141 -0
  48. package/node_modules/event-pubsub/licence +21 -0
  49. package/node_modules/event-pubsub/package.json +59 -0
  50. package/node_modules/strong-type/README.md +408 -0
  51. package/node_modules/strong-type/assets/strong-type-header.png +0 -0
  52. package/node_modules/strong-type/index.js +1151 -0
  53. package/node_modules/strong-type/licence +21 -0
  54. package/node_modules/strong-type/node.js +125 -0
  55. package/node_modules/strong-type/package.json +61 -0
  56. package/package.json +95 -0
  57. package/runtime/ARCANE_RUNTIME_RELEASE.json +791 -0
  58. package/runtime/arcane/components/app-bar.html +468 -0
  59. package/runtime/arcane/components/assistant-panel.html +715 -0
  60. package/runtime/arcane/components/calculator.html +8 -0
  61. package/runtime/arcane/components/chart.html +655 -0
  62. package/runtime/arcane/components/chat.html +1225 -0
  63. package/runtime/arcane/components/conversation-view.html +13 -0
  64. package/runtime/arcane/components/dashboard-config.html +341 -0
  65. package/runtime/arcane/components/data-maintenance.html +112 -0
  66. package/runtime/arcane/components/data-view.html +92 -0
  67. package/runtime/arcane/components/directory-picker.html +197 -0
  68. package/runtime/arcane/components/document-inspector.html +252 -0
  69. package/runtime/arcane/components/file-drop.html +264 -0
  70. package/runtime/arcane/components/file-inspector.html +293 -0
  71. package/runtime/arcane/components/file-manager.html +1715 -0
  72. package/runtime/arcane/components/header.html +142 -0
  73. package/runtime/arcane/components/integration-settings.html +14 -0
  74. package/runtime/arcane/components/local-ai-status.html +360 -0
  75. package/runtime/arcane/components/markdown-document.html +1048 -0
  76. package/runtime/arcane/components/markdown-editor.html +360 -0
  77. package/runtime/arcane/components/media-embed.html +8 -0
  78. package/runtime/arcane/components/modal.html +402 -0
  79. package/runtime/arcane/components/output-panel.html +259 -0
  80. package/runtime/arcane/components/preferences-form.html +135 -0
  81. package/runtime/arcane/components/record-timeline.html +105 -0
  82. package/runtime/arcane/components/relationship-board.html +116 -0
  83. package/runtime/arcane/components/screen-capture.html +8 -0
  84. package/runtime/arcane/components/source-code-viewer.html +441 -0
  85. package/runtime/arcane/components/source-explanation.html +124 -0
  86. package/runtime/arcane/components/speech.html +365 -0
  87. package/runtime/arcane/components/summary-strip.html +177 -0
  88. package/runtime/arcane/components/table.html +77 -0
  89. package/runtime/arcane/components/task-progress.html +282 -0
  90. package/runtime/arcane/components/terminal-workspace.html +65 -0
  91. package/runtime/arcane/components/theme-editor.html +41 -0
  92. package/runtime/arcane/components/theme-switcher.html +46 -0
  93. package/runtime/arcane/components/unified-inbox.html +20 -0
  94. package/runtime/arcane/components/voice-transcription.html +476 -0
  95. package/runtime/arcane/components/weather-widget.html +8 -0
  96. package/runtime/arcane/components/web-navigator.html +239 -0
  97. package/runtime/arcane/css/communications.css +1 -0
  98. package/runtime/arcane/css/dashboard-config.css +45 -0
  99. package/runtime/arcane/css/document-site.css +981 -0
  100. package/runtime/arcane/css/layout.css +438 -0
  101. package/runtime/arcane/css/primitives.css +321 -0
  102. package/runtime/arcane/css/theme.css +112 -0
  103. package/runtime/arcane/css/utility-workspace.css +1 -0
  104. package/runtime/arcane/entities/ApiModelRecord.js +20 -0
  105. package/runtime/arcane/entities/Calculation.js +13 -0
  106. package/runtime/arcane/entities/Chat.js +581 -0
  107. package/runtime/arcane/entities/CommunicationMessage.js +29 -0
  108. package/runtime/arcane/entities/CommunicationThread.js +21 -0
  109. package/runtime/arcane/entities/Document.js +10 -0
  110. package/runtime/arcane/entities/File.js +143 -0
  111. package/runtime/arcane/entities/Image.js +135 -0
  112. package/runtime/arcane/entities/IntentEnvelope.js +834 -0
  113. package/runtime/arcane/entities/Preference.js +83 -0
  114. package/runtime/arcane/entities/TWiNPolicyDecision.js +1092 -0
  115. package/runtime/arcane/entities/TerminalSession.js +46 -0
  116. package/runtime/arcane/entities/Theme.js +107 -0
  117. package/runtime/arcane/entities/User.js +1046 -0
  118. package/runtime/arcane/entities/Weather.js +23 -0
  119. package/runtime/arcane/img/arcane-os-everywhere.png +0 -0
  120. package/runtime/arcane/img/arrow-left.png +0 -0
  121. package/runtime/arcane/img/arrow-right.png +0 -0
  122. package/runtime/arcane/img/doc.svg +5 -0
  123. package/runtime/arcane/img/folder.svg +4 -0
  124. package/runtime/arcane/img/image.svg +5 -0
  125. package/runtime/arcane/img/refresh.png +0 -0
  126. package/runtime/arcane/img/send.svg +9 -0
  127. package/runtime/arcane/img/trash.svg +5 -0
  128. package/runtime/arcane/img/upload.svg +5 -0
  129. package/runtime/arcane/modules/AI.js +2048 -0
  130. package/runtime/arcane/modules/AIPreferenceRuntime.js +32 -0
  131. package/runtime/arcane/modules/AIPreferenceTuple.js +92 -0
  132. package/runtime/arcane/modules/AIResponseLength.js +42 -0
  133. package/runtime/arcane/modules/AIResponseURLPolicy.js +626 -0
  134. package/runtime/arcane/modules/AnsiText.js +53 -0
  135. package/runtime/arcane/modules/ApiModelDatabase.js +25 -0
  136. package/runtime/arcane/modules/AppDataScope.js +245 -0
  137. package/runtime/arcane/modules/AppearancePreferences.js +28 -0
  138. package/runtime/arcane/modules/ArcaneCommunicationBridge.js +22 -0
  139. package/runtime/arcane/modules/ArcaneNavigationPolicy.js +135 -0
  140. package/runtime/arcane/modules/ArcaneNetworkPolicy.js +255 -0
  141. package/runtime/arcane/modules/AsyncBoundary.js +161 -0
  142. package/runtime/arcane/modules/BrowserTestSuite.js +326 -0
  143. package/runtime/arcane/modules/CalculatorEngine.js +20 -0
  144. package/runtime/arcane/modules/CaseEvidenceIndexer.js +134 -0
  145. package/runtime/arcane/modules/ChartLibrary.js +35 -0
  146. package/runtime/arcane/modules/ChatRecords.js +13 -0
  147. package/runtime/arcane/modules/CommunicationAppController.js +43 -0
  148. package/runtime/arcane/modules/CommunicationHub.js +16 -0
  149. package/runtime/arcane/modules/CommunicationPreferences.js +13 -0
  150. package/runtime/arcane/modules/CommunicationProviderRegistry.js +16 -0
  151. package/runtime/arcane/modules/ComponentContracts.js +586 -0
  152. package/runtime/arcane/modules/ConfiguredAIChatSession.js +288 -0
  153. package/runtime/arcane/modules/ConversationActionItems.js +488 -0
  154. package/runtime/arcane/modules/ConversationClosingReport.js +274 -0
  155. package/runtime/arcane/modules/ConversationTimebox.js +527 -0
  156. package/runtime/arcane/modules/CoreLocalModelCatalog.js +255 -0
  157. package/runtime/arcane/modules/DBLS.js +171 -0
  158. package/runtime/arcane/modules/DBOPFS.js +1154 -0
  159. package/runtime/arcane/modules/DBOPFSWorker.js +116 -0
  160. package/runtime/arcane/modules/DataMaintenance.js +91 -0
  161. package/runtime/arcane/modules/DevelopmentWorkspace.js +74 -0
  162. package/runtime/arcane/modules/DirectoryPicker.js +109 -0
  163. package/runtime/arcane/modules/DocumentNavigation.js +223 -0
  164. package/runtime/arcane/modules/Errors.js +1025 -0
  165. package/runtime/arcane/modules/GifEncoder.js +29 -0
  166. package/runtime/arcane/modules/HTMLImport.js +114 -0
  167. package/runtime/arcane/modules/InMemoryCommunicationProvider.js +14 -0
  168. package/runtime/arcane/modules/IsolatedModelQuestionRunner.js +275 -0
  169. package/runtime/arcane/modules/LocalAIReadiness.js +870 -0
  170. package/runtime/arcane/modules/LocalAIReadinessController.js +156 -0
  171. package/runtime/arcane/modules/MD.js +111 -0
  172. package/runtime/arcane/modules/Mail.js +352 -0
  173. package/runtime/arcane/modules/MailTransport.mjs +180 -0
  174. package/runtime/arcane/modules/Marked.min.js +71 -0
  175. package/runtime/arcane/modules/MemoryRecords.js +44 -0
  176. package/runtime/arcane/modules/MessageAdvisory.js +38 -0
  177. package/runtime/arcane/modules/ModelDefinition.js +189 -0
  178. package/runtime/arcane/modules/Ollama.js +74 -0
  179. package/runtime/arcane/modules/OllamaModelIdentifier.js +21 -0
  180. package/runtime/arcane/modules/OllamaSettings.js +24 -0
  181. package/runtime/arcane/modules/OpenMeteoWeatherProvider.js +14 -0
  182. package/runtime/arcane/modules/PreferenceStore.js +109 -0
  183. package/runtime/arcane/modules/QRCode.min.js +1 -0
  184. package/runtime/arcane/modules/Questionnaire.js +61 -0
  185. package/runtime/arcane/modules/RecordLinkIndex.js +24 -0
  186. package/runtime/arcane/modules/RecordPassageIndex.js +222 -0
  187. package/runtime/arcane/modules/RecordReviewStore.js +94 -0
  188. package/runtime/arcane/modules/RevocableProjectionLedger.js +1623 -0
  189. package/runtime/arcane/modules/RiskSignalAnalyzer.js +37 -0
  190. package/runtime/arcane/modules/ScamRiskPolicy.js +62 -0
  191. package/runtime/arcane/modules/ScopedOPFSCache.js +183 -0
  192. package/runtime/arcane/modules/ScreenCapture.js +20 -0
  193. package/runtime/arcane/modules/SpeechPlayback.js +581 -0
  194. package/runtime/arcane/modules/StaticDocumentCatalog.js +1248 -0
  195. package/runtime/arcane/modules/SystemAppearance.js +20 -0
  196. package/runtime/arcane/modules/SystemPlatformPresentation.js +51 -0
  197. package/runtime/arcane/modules/SystemToolRegistry.js +28 -0
  198. package/runtime/arcane/modules/TerminalClient.js +52 -0
  199. package/runtime/arcane/modules/TerminalCommandRegistry.js +50 -0
  200. package/runtime/arcane/modules/ThemeBootstrap.js +26 -0
  201. package/runtime/arcane/modules/ThemeManager.js +131 -0
  202. package/runtime/arcane/modules/TimeGuard.js +149 -0
  203. package/runtime/arcane/modules/ToolCallRouter.js +83 -0
  204. package/runtime/arcane/modules/WaitForComponent.js +102 -0
  205. package/runtime/arcane/modules/YouTubeMedia.js +16 -0
  206. package/runtime/arcane/modules/uPlot.LICENSE.txt +21 -0
  207. package/runtime/arcane/modules/uPlot.iife.min.js +2 -0
  208. package/runtime/arcane/modules/uPlot.min.css +1 -0
  209. package/runtime/arcane/security/arcane-network-policy.json +6 -0
  210. package/runtime/strong-type/index.js +352 -0
  211. package/runtime/strong-type/licence +21 -0
  212. package/runtime/strong-type/package.json +45 -0
  213. package/schemas/arcane-app-bundle.schema.json +125 -0
  214. package/schemas/arcane-app.schema.json +329 -0
  215. package/schemas/arcane-lock.schema.json +86 -0
  216. package/schemas/arcane-package.schema.json +224 -0
  217. package/schemas/cli-event.schema.json +122 -0
  218. package/schemas/event-stack.schema.json +152 -0
  219. package/schemas/native-build-plan.schema.json +176 -0
  220. package/schemas/target-adapter.schema.json +117 -0
  221. package/src/app-descriptor.mjs +500 -0
  222. package/src/cli/main.mjs +561 -0
  223. package/src/constants.mjs +31 -0
  224. package/src/dev-server.mjs +718 -0
  225. package/src/doctor.mjs +315 -0
  226. package/src/dom-event-instrumentation.mjs +594 -0
  227. package/src/errors.mjs +75 -0
  228. package/src/event-manager.mjs +1342 -0
  229. package/src/event-queue.mjs +138 -0
  230. package/src/events.mjs +219 -0
  231. package/src/index.mjs +177 -0
  232. package/src/integrated-provider-loader.mjs +432 -0
  233. package/src/native-plan.mjs +698 -0
  234. package/src/native-provider-loader.mjs +1126 -0
  235. package/src/packager/core.mjs +2691 -0
  236. package/src/process.mjs +353 -0
  237. package/src/release-bundle.mjs +2523 -0
  238. package/src/repository.mjs +90 -0
  239. package/src/runtime.mjs +452 -0
  240. package/src/scaffold.mjs +380 -0
  241. package/src/targets/index.mjs +436 -0
  242. package/src/templates/assets/app-icon.png +0 -0
  243. package/src/templates/workspace-template.mjs +388 -0
  244. package/src/testing-loader.mjs +9 -0
  245. package/src/testing.mjs +427 -0
  246. package/src/toolchain.mjs +1335 -0
  247. package/src/update-check.mjs +307 -0
  248. 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
+ ```