arcane-os 0.41.0 → 0.42.1

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 CHANGED
@@ -1,5 +1,27 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.42.1
4
+
5
+ - Keep Enter as native newline input on phones and tablets using the shared
6
+ browser-device classification. The visible Send button still submits.
7
+ - Submit desktop Enter before native newline insertion while preserving
8
+ Shift+Enter, input-method composition, complete draft text, and existing
9
+ Send, speech and programmatic submission routes. Held Enter does not submit
10
+ repeatedly; input changes resize the composer through its existing lifecycle.
11
+
12
+ ## 0.42.0
13
+
14
+ - Accept the optional boolean `responseToUsersPromptComplete` in conversation
15
+ closing-report arguments, preserving both `true` and `false` and leaving the
16
+ normalized record unchanged when the field is omitted.
17
+ - Add `createConversationClosingReportTool({responseToUsersPromptComplete:
18
+ {required:true,description}})` so applications can require the completion
19
+ flag and supply its complete description. Omitting the option preserves the
20
+ existing schema; an optional declaration defaults `required` to `false`.
21
+ Applications continue to own tool execution,
22
+ continuation, terminal closeout and storage; the SDK adds no runner or
23
+ automatic follow-up behavior and performs no saved-history migration.
24
+
3
25
  ## 0.41.0
4
26
 
5
27
  - Add `createTwinCloudTTSProvider` through `arcane-os/ai/browser-speech`,
package/README.md CHANGED
@@ -19,7 +19,7 @@ version-locked SDK runtime, while an integrated Arcane checkout uses its live
19
19
  `arcane/` runtime. Both profiles share the theme, packaging, event, cancellation,
20
20
  and browser run contracts while retaining their selected application layout.
21
21
 
22
- This checkout defines the `0.41.0` SDK contract. Applications pin one exact npm
22
+ This checkout defines the `0.42.1` SDK contract. Applications pin one exact npm
23
23
  version and lockfile; registry state is deliberately not baked into application
24
24
  artifacts.
25
25
 
@@ -489,8 +489,8 @@
489
489
  "summary": "Defines the closing-report tool, instruction, result normalizer, call classifier, and formatter.",
490
490
  "availability": "Cross-host",
491
491
  "protocol": "In-process only",
492
- "normalization": "Required user-facing message remains distinct from the complete final_message; normalized results expose message, finalMessage, and rememberedActions.",
493
- "surface": "Seven constants/helpers for closing reports; the sole-call schema requires message and final_message. formatConversationClosingReportText(value) exposes the existing &, <, and > escaping for any string, including whitespace-only chunks, and the complete report formatter reuses it."
492
+ "normalization": "Required user-facing message remains distinct from the complete final_message; normalized results expose message, finalMessage, and rememberedActions. Optional responseToUsersPromptComplete is preserved only when supplied as a boolean, with no coercion or default; it is transient app-owned control metadata outside saved history.",
493
+ "surface": "Seven constants/helpers for closing reports; the sole-call schema requires message and final_message. createConversationClosingReportTool optionally accepts responseToUsersPromptComplete:{required:false,description:<nonblank app-owned string>} to declare the boolean, or required:true to require it; omission preserves the existing schema. The normalizer remains independent of factory options and orchestration remains app-owned. formatConversationClosingReportText(value) exposes the existing &, <, and > escaping for any string, including whitespace-only chunks, and the complete report formatter reuses it."
494
494
  },
495
495
  {
496
496
  "file": "runtime/arcane/modules/ConversationTimebox.js",
@@ -314,6 +314,13 @@ extension callbacks. The component installs warning-only defaults when the host
314
314
  does not supply them; applications may instead consume the corresponding
315
315
  `chat-send-message` and `chat-language-changed` events.
316
316
 
317
+ The composer uses the shared browser-device classification: Enter inserts a
318
+ newline on mobile devices and tablets; desktop Enter submits before the native
319
+ newline is inserted. Shift+Enter and input-method composition retain native
320
+ editing. The visible Send button remains available on every device. Input
321
+ changes resize the composer without submitting; held desktop Enter does not
322
+ submit repeatedly. All input listeners follow the component's existing cleanup.
323
+
317
324
  Before any session binding begins, callback-mode `sessionStatus` follows the
318
325
  selected sticky LLM role and the existing application-owned `modelName` label
319
326
  (or the selected model ID). Assigning `modelName` refreshes that status even
@@ -1933,6 +1933,35 @@ replaces `&`, `<`, and `>` with `&amp;`, `&lt;`, and `&gt;` respectively. It
1933
1933
  preserves all other text and does not trim, validate a report, render, or persist
1934
1934
  anything. Use the same operation for live text chunks and complete final text.
1935
1935
 
1936
+ An application may opt into completion metadata when creating the tool:
1937
+
1938
+ ```javascript
1939
+ import {createConversationClosingReportTool} from 'arcane-os/conversation-closing-report';
1940
+
1941
+ const tool=createConversationClosingReportTool({
1942
+ responseToUsersPromptComplete:{
1943
+ required:true,
1944
+ description:"Use true only when this response's messages and successful tool actions complete the whole current user request. Use false if any answer or action still needs a model response, whether or not this tool returns data."
1945
+ }
1946
+ });
1947
+ ```
1948
+
1949
+ The option declares a boolean `responseToUsersPromptComplete` argument. Its
1950
+ `description` must be a nonblank string and is preserved completely;
1951
+ `required` defaults to `false`. Omitting the option preserves the existing
1952
+ schema, including its required fields. This changes only the declaration, not
1953
+ the closeout instruction or sole-call classifier.
1954
+
1955
+ `normalizeConversationClosingReport()` accepts that optional boolean in an
1956
+ object or JSON string and preserves `true` or `false` in the returned record
1957
+ under the same key. Omission leaves the key absent; other supplied types raise
1958
+ `CONVERSATION_CLOSING_REPORT_INVALID` without coercion. The normalizer does not
1959
+ consume factory options or enforce an application's required setting. The
1960
+ application owns completion decisions, sibling-call handling, and continuation.
1961
+ The flag is transient control metadata, not conversation content or a saved
1962
+ history field. These helpers perform no persistence or orchestration, and the
1963
+ formatter still returns only the formatted `finalMessage`.
1964
+
1936
1965
  Exact exports: `CONVERSATION_CLOSING_REPORT_TOOL_NAME`, `classifyConversationClosingReportCalls`, `conversationClosingReportInstruction`, `createConversationClosingReportTool`, `formatConversationClosingReport`, `formatConversationClosingReportText`, `normalizeConversationClosingReport`.
1937
1966
 
1938
1967
  ### Availability and normalization
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "arcane-os",
3
- "version": "0.41.0",
3
+ "version": "0.42.1",
4
4
  "description": "Arcane OS JavaScript SDK, project-local CLI, browser runtime, and repository-portable application packager.",
5
5
  "type": "module",
6
6
  "main": "./src/index.mjs",
@@ -85,8 +85,8 @@
85
85
  "scripts": {
86
86
  "test": "npm run test:unit && npm run test:functional && npm run test:integration && npm run test:regression",
87
87
  "test:release": "node ./bin/arcane-test.mjs test/npm-release.test.mjs",
88
- "test:unit": "node ./bin/arcane-test.mjs test/app-descriptor.test.mjs test/app-schema.test.mjs test/app-selection.test.mjs test/contracts.test.mjs test/doctor.test.mjs test/mail-credentials.test.mjs test/mail-outbox.test.mjs test/mail-public-api.test.mjs test/mail-send.test.mjs test/mail-transport.test.mjs test/targets.test.mjs test/workspace-operation-lock.test.mjs",
89
- "test:functional": "node ./bin/arcane-test.mjs test/ai-request-model.test.mjs test/ai-request-retry.test.mjs test/ai-stream-cancellation.test.mjs test/browser-speech-providers.test.mjs test/browser-wasm-gpu-notice.test.mjs test/browser-wasm-download-resume.test.mjs test/chat-stream-speech.test.mjs test/chat-transcript-scroll.test.mjs test/chat-transient-retention.test.mjs test/cli.test.mjs test/continuous-voice-capture.test.mjs test/dbopfs-document-library.test.mjs test/dbopfs-png.test.mjs test/dbopfs.test.mjs test/dev-server.test.mjs test/dev-pwa.test.mjs test/dom-event-instrumentation.test.mjs test/event-manager.test.mjs test/events.test.mjs test/file-preview.test.mjs test/import-map.test.mjs test/mail-cli.test.mjs test/mail-runtime.test.mjs test/mail-server.test.mjs test/markdown-editor.test.mjs test/markdown-source-map.test.mjs test/modal.test.mjs test/packaging.test.mjs test/pwa-packaging.test.mjs test/pwa-client.test.mjs test/pwa-install.test.mjs test/pwa-worker.test.mjs test/persistent-ai-chat-session.test.mjs test/reference-completeness.test.mjs test/runtime-api-behavior.test.mjs test/runtime.test.mjs test/scaffold.test.mjs test/speech-playback.test.mjs test/site.test.mjs test/twin-cloud.test.mjs test/update-check.test.mjs test/user-explicit-updates.test.mjs",
88
+ "test:unit": "node ./bin/arcane-test.mjs test/app-descriptor.test.mjs test/app-schema.test.mjs test/app-selection.test.mjs test/contracts.test.mjs test/conversation-closing-report.test.mjs test/doctor.test.mjs test/mail-credentials.test.mjs test/mail-outbox.test.mjs test/mail-public-api.test.mjs test/mail-send.test.mjs test/mail-transport.test.mjs test/targets.test.mjs test/workspace-operation-lock.test.mjs",
89
+ "test:functional": "node ./bin/arcane-test.mjs test/ai-request-model.test.mjs test/ai-request-retry.test.mjs test/ai-stream-cancellation.test.mjs test/browser-speech-providers.test.mjs test/browser-wasm-gpu-notice.test.mjs test/browser-wasm-download-resume.test.mjs test/chat-input.test.mjs test/chat-stream-speech.test.mjs test/chat-transcript-scroll.test.mjs test/chat-transient-retention.test.mjs test/cli.test.mjs test/continuous-voice-capture.test.mjs test/dbopfs-document-library.test.mjs test/dbopfs-png.test.mjs test/dbopfs.test.mjs test/dev-server.test.mjs test/dev-pwa.test.mjs test/dom-event-instrumentation.test.mjs test/event-manager.test.mjs test/events.test.mjs test/file-preview.test.mjs test/import-map.test.mjs test/mail-cli.test.mjs test/mail-runtime.test.mjs test/mail-server.test.mjs test/markdown-editor.test.mjs test/markdown-source-map.test.mjs test/modal.test.mjs test/packaging.test.mjs test/pwa-packaging.test.mjs test/pwa-client.test.mjs test/pwa-install.test.mjs test/pwa-worker.test.mjs test/persistent-ai-chat-session.test.mjs test/reference-completeness.test.mjs test/runtime-api-behavior.test.mjs test/runtime.test.mjs test/scaffold.test.mjs test/speech-playback.test.mjs test/site.test.mjs test/twin-cloud.test.mjs test/update-check.test.mjs test/user-explicit-updates.test.mjs",
90
90
  "test:integration": "node ./bin/arcane-test.mjs test/installed-package-runtime.test.mjs test/root-app-layout.test.mjs test/integrated-shared.test.mjs test/integrated-workspace.test.mjs test/mail-browser.test.mjs test/native-plan.test.mjs test/native-provider-loader.test.mjs test/npm-release.test.mjs test/release-bundle.test.mjs test/release-capability-smoke.test.mjs test/shared-payload-batch.test.mjs test/tarball.test.mjs test/browser-wasm-cpu.test.mjs test/wllama-webgpu-runtime.test.mjs",
91
91
  "test:regression": "node ./bin/arcane-test.mjs test/channel-workflows.test.mjs test/html-import-registration.test.mjs test/logging-regression.test.mjs test/markdown-speech.test.mjs test/prepared-speech.test.mjs test/native-provider-generation.test.mjs test/speech-queue-regression.test.mjs test/testing.test.mjs test/test-sets.test.mjs",
92
92
  "check": "node tools/check-source.mjs && npm test",
@@ -889,14 +889,17 @@
889
889
  {default:FileEntity},
890
890
  {ConversationSubmissionBarrier,requireConversationTimeboxDelivery},
891
891
  {requestAIRuntimeIntent,subscribeAIRuntimeState},
892
- {createPersistentAIChatSession}
892
+ {createPersistentAIChatSession},
893
+ {getBrowserDeviceClass}
893
894
  ]=await Promise.all([
894
895
  import('../modules/MD.js'),
895
896
  import('../entities/File.js'),
896
897
  import('../modules/ConversationTimebox.js'),
897
898
  import('../modules/AIRuntimeState.js'),
898
- import('../modules/PersistentAIChatSession.js')
899
+ import('../modules/PersistentAIChatSession.js'),
900
+ import('arcane-os/browser-device')
899
901
  ]);
902
+ const browserDeviceClass = getBrowserDeviceClass();
900
903
 
901
904
  host.language=host.language||'English';
902
905
  host.name = host.name||'User';
@@ -2997,7 +3000,13 @@
2997
3000
  }
2998
3001
 
2999
3002
  textArea.addEventListener(
3000
- 'keyup',
3003
+ 'keydown',
3004
+ handleChatKeyDown,
3005
+ {signal:aiRuntimeStateAbortController.signal}
3006
+ );
3007
+
3008
+ textArea.addEventListener(
3009
+ 'input',
3001
3010
  resizeTextArea,
3002
3011
  {signal:aiRuntimeStateAbortController.signal}
3003
3012
  );
@@ -3140,12 +3149,28 @@
3140
3149
  {signal:aiRuntimeStateAbortController.signal}
3141
3150
  );
3142
3151
 
3143
- async function resizeTextArea(e={}) {
3144
- if(e.key && e.key == 'Enter' && !e.shiftKey && !send.disabled){
3145
- e.preventDefault();
3146
- void submitMessage('',{source:'keyboard'});
3152
+ function handleChatKeyDown(event) {
3153
+ if(browserDeviceClass === 'mobile'
3154
+ || event.key !== 'Enter'
3155
+ || event.shiftKey
3156
+ || event.isComposing
3157
+ || event.keyCode === 229
3158
+ || send.disabled) {
3159
+ return;
3160
+ }
3161
+
3162
+ event.preventDefault();
3163
+ if(event.repeat) {
3164
+ return;
3147
3165
  }
3166
+ void submitMessage(
3167
+ '',
3168
+ {source: 'keyboard'}
3169
+ );
3170
+ resizeTextArea();
3171
+ }
3148
3172
 
3173
+ async function resizeTextArea() {
3149
3174
  // Reset height to shrink to content size
3150
3175
  trackTranscriptScrollPosition();
3151
3176
  textArea.style.height = 'auto';
@@ -11,7 +11,8 @@ const TOOL_NAME_PATTERN=/^[a-z][a-z0-9_]*$/;
11
11
  const REPORT_FIELDS=new Set([
12
12
  'message',
13
13
  'final_message',
14
- 'remembered_actions'
14
+ 'remembered_actions',
15
+ 'responseToUsersPromptComplete'
15
16
  ]);
16
17
 
17
18
  function invalid(message){
@@ -88,11 +89,14 @@ function completeValue(value){
88
89
  /**
89
90
  * Creates a provider-neutral terminal tool for one user-facing conversation
90
91
  * closeout. The tool performs no inference, persistence, rendering, task
91
- * execution, navigation, or external delivery.
92
+ * execution, navigation, or external delivery. An optional
93
+ * responseToUsersPromptComplete declaration adds app-owned completion metadata;
94
+ * its description is required and its required setting defaults to false.
92
95
  */
93
96
  export function createConversationClosingReportTool({
94
97
  name=DEFAULT_TOOL_NAME,
95
- description='Prepare a complete closing report when the user clearly ends a conversation.'
98
+ description='Prepare a complete closing report when the user clearly ends a conversation.',
99
+ responseToUsersPromptComplete
96
100
  }={}){
97
101
  if(!is.string(name)||!TOOL_NAME_PATTERN.test(name)){
98
102
  throw invalid('The closing-report tool name is invalid.');
@@ -101,7 +105,7 @@ export function createConversationClosingReportTool({
101
105
  throw invalid('The closing-report tool description is invalid.');
102
106
  }
103
107
 
104
- return completeValue({
108
+ const tool={
105
109
  type:'function',
106
110
  function:{
107
111
  name,
@@ -145,7 +149,33 @@ export function createConversationClosingReportTool({
145
149
  required:['message','final_message']
146
150
  }
147
151
  }
148
- });
152
+ };
153
+
154
+ if(responseToUsersPromptComplete!==undefined){
155
+ if(!isPlainRecord(responseToUsersPromptComplete)){
156
+ throw invalid('responseToUsersPromptComplete options must be an object.');
157
+ }
158
+ const {
159
+ required=false,
160
+ description:completionDescription
161
+ }=responseToUsersPromptComplete;
162
+ if(!is.boolean(required)){
163
+ throw invalid('responseToUsersPromptComplete.required must be a boolean.');
164
+ }
165
+ tool.function.parameters.properties.responseToUsersPromptComplete={
166
+ type:'boolean',
167
+ description:normalizedText(
168
+ completionDescription,
169
+ 'responseToUsersPromptComplete.description',
170
+ {required:true}
171
+ )
172
+ };
173
+ if(required){
174
+ tool.function.parameters.required.push('responseToUsersPromptComplete');
175
+ }
176
+ }
177
+
178
+ return completeValue(tool);
149
179
  }
150
180
 
151
181
  /**
@@ -181,13 +211,22 @@ export function normalizeConversationClosingReport(value){
181
211
  throw invalid(`Unexpected closing-report field: ${unexpectedField}.`);
182
212
  }
183
213
 
184
- return completeValue({
214
+ const report={
185
215
  message:normalizedText(source.message,'message',{required:true}),
186
216
  finalMessage:normalizedFinalMessage(source.final_message),
187
217
  rememberedActions:normalizeRememberedConversationActions(
188
218
  source.remembered_actions||[]
189
219
  )
190
- });
220
+ };
221
+
222
+ if(Object.hasOwn(source,'responseToUsersPromptComplete')){
223
+ if(!is.boolean(source.responseToUsersPromptComplete)){
224
+ throw invalid('responseToUsersPromptComplete must be a boolean.');
225
+ }
226
+ report.responseToUsersPromptComplete=source.responseToUsersPromptComplete;
227
+ }
228
+
229
+ return completeValue(report);
191
230
  }
192
231
 
193
232
  /**
@@ -233,13 +272,17 @@ export function classifyConversationClosingReportCalls(calls={}, {
233
272
  }
234
273
 
235
274
  export function formatConversationClosingReport(value){
236
- const normalizedInput=isPlainRecord(value)&&value.finalMessage!==undefined
237
- ?{
275
+ let normalizedInput=value;
276
+ if(isPlainRecord(value)&&value.finalMessage!==undefined){
277
+ normalizedInput={
238
278
  message:value.message,
239
279
  final_message:value.finalMessage,
240
280
  remembered_actions:value.rememberedActions,
281
+ };
282
+ if(Object.hasOwn(value,'responseToUsersPromptComplete')){
283
+ normalizedInput.responseToUsersPromptComplete=value.responseToUsersPromptComplete;
241
284
  }
242
- :value;
285
+ }
243
286
  return formatConversationClosingReportText(
244
287
  normalizeConversationClosingReport(normalizedInput).finalMessage
245
288
  );