arcane-os 0.3.0 → 0.3.2
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 +27 -0
- package/README.md +86 -117
- package/bin/arcane-test.mjs +170 -46
- package/browser-runtime/ai/browser-speech-artifacts.mjs +887 -909
- package/browser-runtime/ai/browser-speech-providers.mjs +96 -152
- package/browser-runtime/ai/browser-wasm-llm-provider.mjs +627 -819
- package/browser-runtime/ai/browser-wasm.mjs +24 -35
- package/browser-runtime/ai/browser-wllama-runtime.mjs +64 -316
- package/browser-runtime/ai/model-controller.mjs +584 -181
- package/browser-runtime/ai/speech-worker-client.mjs +8 -146
- package/browser-runtime/ai/speech-worker-runtime.mjs +643 -363
- package/browser-runtime/dom-event-instrumentation.mjs +55 -147
- package/browser-runtime/event-manager.mjs +239 -624
- package/package.json +5 -6
- package/runtime/arcane/components/app-bar.html +3 -15
- package/runtime/arcane/components/assistant-panel.html +10 -10
- package/runtime/arcane/components/calculator.html +1 -1
- package/runtime/arcane/components/chat.html +1359 -135
- package/runtime/arcane/components/conversation-view.html +2 -2
- package/runtime/arcane/components/document-inspector.html +11 -17
- package/runtime/arcane/components/file-manager.html +13 -56
- package/runtime/arcane/components/markdown-document.html +82 -281
- package/runtime/arcane/components/markdown-editor.html +7 -10
- package/runtime/arcane/components/media-embed.html +6 -6
- package/runtime/arcane/components/screen-capture.html +4 -4
- package/runtime/arcane/components/source-explanation.html +2 -2
- package/runtime/arcane/components/speech.html +112 -68
- package/runtime/arcane/components/terminal-workspace.html +4 -4
- package/runtime/arcane/components/theme-editor.html +1 -1
- package/runtime/arcane/components/unified-inbox.html +2 -2
- package/runtime/arcane/components/voice-transcription.html +31 -21
- package/runtime/arcane/entities/Calculation.js +2 -3
- package/runtime/arcane/entities/Chat.js +228 -43
- package/runtime/arcane/entities/Preference.js +3 -5
- package/runtime/arcane/entities/Weather.js +5 -5
- package/runtime/arcane/modules/AI.js +1050 -427
- package/runtime/arcane/modules/AIProviderRuntime.js +658 -363
- package/runtime/arcane/modules/AIResponseLength.js +9 -19
- package/runtime/arcane/modules/AIRuntimeState.js +109 -72
- package/runtime/arcane/modules/ArcaneNavigationPolicy.js +45 -32
- package/runtime/arcane/modules/BrowserTestSuite.js +78 -122
- package/runtime/arcane/modules/CalculatorEngine.js +9 -9
- package/runtime/arcane/modules/CommunicationAppController.js +3 -7
- package/runtime/arcane/modules/ComponentContracts.js +30 -32
- package/runtime/arcane/modules/ConfiguredAIChatSession.js +281 -230
- package/runtime/arcane/modules/ConversationActionItems.js +26 -59
- package/runtime/arcane/modules/ConversationClosingReport.js +34 -61
- package/runtime/arcane/modules/ConversationTimebox.js +27 -15
- package/runtime/arcane/modules/DBOPFSDocumentLibrary.js +152 -344
- package/runtime/arcane/modules/DocumentLexicalSearch.js +25 -91
- package/runtime/arcane/modules/HTMLImport.js +54 -1
- package/runtime/arcane/modules/IsolatedModelQuestionRunner.js +40 -203
- package/runtime/arcane/modules/LocalAIReadiness.js +40 -60
- package/runtime/arcane/modules/LocalAIReadinessController.js +15 -13
- package/runtime/arcane/modules/MD.js +1 -45
- package/runtime/arcane/modules/Mail.js +51 -103
- package/runtime/arcane/modules/MailOutbox.mjs +95 -193
- package/runtime/arcane/modules/MailTransport.mjs +36 -57
- package/runtime/arcane/modules/ModelDefinition.js +22 -106
- package/runtime/arcane/modules/OpenMeteoWeatherProvider.js +39 -101
- package/runtime/arcane/modules/PersistentAIChatSession.js +281 -18
- package/runtime/arcane/modules/PreferenceStore.js +102 -30
- package/runtime/arcane/modules/RiskSignalAnalyzer.js +8 -9
- package/runtime/arcane/modules/ScopedOPFSCache.js +7 -42
- package/runtime/arcane/modules/ScreenCapture.js +175 -128
- package/runtime/arcane/modules/SpeechPlayback.js +46 -149
- package/runtime/arcane/modules/StaticDocumentCatalog.js +173 -407
- package/runtime/arcane/modules/ToolCallRouter.js +25 -12
- package/runtime/arcane/modules/YouTubeMedia.js +6 -5
- package/schemas/arcane-app-bundle.schema.json +13 -78
- package/schemas/arcane-app.schema.json +9 -25
- package/schemas/arcane-lock.schema.json +18 -151
- package/schemas/arcane-package.schema.json +2 -16
- package/schemas/native-build-plan.schema.json +119 -122
- package/src/app-descriptor.mjs +75 -132
- package/src/application-tests.mjs +200 -0
- package/src/cli/main.mjs +27 -46
- package/src/constants.mjs +3 -4
- package/src/dev-server.mjs +30 -324
- package/src/doctor.mjs +92 -154
- package/src/dom-event-instrumentation.mjs +55 -147
- package/src/errors.mjs +2 -3
- package/src/event-manager.mjs +239 -624
- package/src/event-queue.mjs +3 -3
- package/src/import-map.mjs +273 -1028
- package/src/index.mjs +14 -16
- package/src/installed-sdk-runtime.mjs +40 -62
- package/src/integrated-provider-loader.mjs +53 -382
- package/src/mail-api.mjs +0 -2
- package/src/mail-server.mjs +224 -580
- package/src/mail.mjs +4 -10
- package/src/native-plan.mjs +163 -598
- package/src/native-provider-loader.mjs +104 -1063
- package/src/packager/core.mjs +485 -3229
- package/src/process.mjs +5 -10
- package/src/release-bundle.mjs +292 -2405
- package/src/runtime.mjs +76 -396
- package/src/scaffold.mjs +30 -80
- package/src/sdk-browser-runtime.mjs +70 -626
- package/src/source-server.mjs +588 -0
- package/src/targets/index.mjs +78 -188
- package/src/templates/workspace-template.mjs +19 -135
- package/src/testing-loader.mjs +164 -0
- package/src/testing.mjs +1 -1
- package/src/toolchain.mjs +131 -544
- package/src/update-check.mjs +26 -64
- package/src/workspace-operation-lock.mjs +139 -430
- package/src/workspace-runtime.mjs +112 -779
- package/src/workspace.mjs +40 -302
- package/browser-runtime/ARCANE_SDK_BROWSER_RELEASE.json +0 -218
- package/browser-runtime/ai/ARCANE_AI_BROWSER_SPEECH_COMPONENTS.json +0 -203
- package/browser-runtime/ai/ARCANE_AI_BROWSER_WASM_COMPONENTS.json +0 -80
- package/browser-runtime/ai/internal/sha256.mjs +0 -166
- package/docs/architecture.md +0 -344
- package/docs/compatibility.md +0 -36
- package/docs/event-manager.md +0 -294
- package/docs/platform-targets.md +0 -108
- package/docs/publishing.md +0 -201
- package/docs/reference/README.md +0 -187
- package/docs/reference/ai/browser-speech-package-authority.json +0 -835
- package/docs/reference/ai/browser-speech.md +0 -1252
- package/docs/reference/ai/browser-wasm.md +0 -530
- package/docs/reference/arcane-ollama.md +0 -288
- package/docs/reference/availability-and-normalization.md +0 -183
- package/docs/reference/behavioral-testing.md +0 -133
- package/docs/reference/cli.md +0 -779
- package/docs/reference/core/README.md +0 -62
- package/docs/reference/core/arcane-ai-contracts.md +0 -907
- package/docs/reference/core/arcane-api.md +0 -601
- package/docs/reference/core/arcane-entities.md +0 -65
- package/docs/reference/core/arcane-events.md +0 -134
- package/docs/reference/core/ollama-module.md +0 -181
- package/docs/reference/core/reference/arcane-api/ai-and-ollama.md +0 -1909
- package/docs/reference/core/reference/arcane-api/applications-terminal-capabilities.md +0 -1057
- package/docs/reference/core/reference/arcane-api/core-and-events.md +0 -320
- package/docs/reference/core/reference/arcane-api/filesystem-storage-preferences-appearance.md +0 -610
- package/docs/reference/core/reference/arcane-api/namespaces.md +0 -1157
- package/docs/reference/core/reference/arcane-api/platform-installation-users-system.md +0 -1423
- package/docs/reference/core/reference/arcane-api/session-provisioning-diagnostics-development.md +0 -315
- package/docs/reference/event-manager.md +0 -1511
- package/docs/reference/inventory/package-api.json +0 -3284
- package/docs/reference/inventory/runtime-components.json +0 -1011
- package/docs/reference/inventory/runtime-entities.json +0 -26
- package/docs/reference/inventory/runtime-modules.json +0 -1431
- package/docs/reference/mail.md +0 -316
- package/docs/reference/protocols.md +0 -677
- package/docs/reference/runtime-components.md +0 -1366
- package/docs/reference/runtime-entities.md +0 -303
- package/docs/reference/runtime-modules.md +0 -2960
- package/docs/reference/sdk-api.md +0 -6694
- package/docs/roadmap.md +0 -79
- package/docs/work-amplification.md +0 -129
- package/runtime/ARCANE_RUNTIME_RELEASE.json +0 -826
|
@@ -1,12 +1,8 @@
|
|
|
1
|
-
const ACTION_ITEM_STATUSES=
|
|
2
|
-
const ACTION_ITEM_BASES=
|
|
3
|
-
const MAX_ACTION_ITEMS=50;
|
|
4
|
-
const MAX_REMEMBERED_ACTIONS=6;
|
|
5
|
-
const MAX_ACTION_ITEM_CHARACTERS=500;
|
|
6
|
-
const MAX_PRESENTED_ACTION_ITEMS=1;
|
|
1
|
+
const ACTION_ITEM_STATUSES=['open','completed'];
|
|
2
|
+
const ACTION_ITEM_BASES=['user_commitment','optional_homework'];
|
|
7
3
|
const DEFAULT_PRESENTATION_COOLDOWN_MS=7*24*60*60*1000;
|
|
8
|
-
const ACTION_ITEM_ID_PATTERN=/^[A-Za-z0-9_-]
|
|
9
|
-
const CONVERSATION_ID_PATTERN=/^[A-Za-z0-9_.-]
|
|
4
|
+
const ACTION_ITEM_ID_PATTERN=/^[A-Za-z0-9_-]+$/;
|
|
5
|
+
const CONVERSATION_ID_PATTERN=/^[A-Za-z0-9_.-]+$/;
|
|
10
6
|
const UNSAFE_TEXT_CONTROL_PATTERN=/[\u200b-\u200f\u202a-\u202e\u2060-\u2069\ufeff]/u;
|
|
11
7
|
|
|
12
8
|
function invalid(message){
|
|
@@ -32,16 +28,10 @@ function normalizedText(value,label='Action item'){
|
|
|
32
28
|
throw invalid(`${label} contains unsafe invisible or bidirectional controls.`);
|
|
33
29
|
}
|
|
34
30
|
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
if(!text){
|
|
31
|
+
if(!value.trim()){
|
|
38
32
|
throw invalid(`${label} is required.`);
|
|
39
33
|
}
|
|
40
|
-
|
|
41
|
-
throw invalid(`${label} exceeds ${MAX_ACTION_ITEM_CHARACTERS} characters.`);
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
return text;
|
|
34
|
+
return value;
|
|
45
35
|
}
|
|
46
36
|
|
|
47
37
|
function normalizedId(value,label='Action item id'){
|
|
@@ -112,20 +102,17 @@ function defaultActionItemId(){
|
|
|
112
102
|
return globalThis.crypto.randomUUID();
|
|
113
103
|
}
|
|
114
104
|
|
|
115
|
-
return `item-${Date.now()}-${Math.random().toString(36).
|
|
105
|
+
return `item-${Date.now()}-${Math.random().toString(36).replace(/^0\./u,'')}`;
|
|
116
106
|
}
|
|
117
107
|
|
|
118
|
-
function
|
|
119
|
-
items
|
|
120
|
-
return Object.freeze(items);
|
|
108
|
+
function completeItems(items){
|
|
109
|
+
return items;
|
|
121
110
|
}
|
|
122
111
|
|
|
123
112
|
export function normalizeRememberedConversationActions(value=[]){
|
|
124
|
-
if(!Array.isArray(value)
|
|
125
|
-
throw invalid(`Remembered actions must contain at most ${MAX_REMEMBERED_ACTIONS} entries.`);
|
|
126
|
-
}
|
|
113
|
+
if(!Array.isArray(value))throw invalid('Remembered actions must be an array.');
|
|
127
114
|
|
|
128
|
-
return
|
|
115
|
+
return completeItems(value.map(function normalizeRememberedAction(action){
|
|
129
116
|
if(!isPlainRecord(action)){
|
|
130
117
|
throw invalid('Remembered action must be an object.');
|
|
131
118
|
}
|
|
@@ -180,7 +167,7 @@ export function normalizeConversationActionItem(value){
|
|
|
180
167
|
throw invalid('Action item timestamps are inconsistent.');
|
|
181
168
|
}
|
|
182
169
|
|
|
183
|
-
return
|
|
170
|
+
return {
|
|
184
171
|
id,
|
|
185
172
|
text,
|
|
186
173
|
basis,
|
|
@@ -195,13 +182,11 @@ export function normalizeConversationActionItem(value){
|
|
|
195
182
|
value.lastPresentedChatId,
|
|
196
183
|
'lastPresentedChatId'
|
|
197
184
|
)
|
|
198
|
-
}
|
|
185
|
+
};
|
|
199
186
|
}
|
|
200
187
|
|
|
201
188
|
export function normalizeConversationActionItems(value=[]){
|
|
202
|
-
if(!Array.isArray(value)
|
|
203
|
-
throw invalid(`Action items must contain at most ${MAX_ACTION_ITEMS} records.`);
|
|
204
|
-
}
|
|
189
|
+
if(!Array.isArray(value))throw invalid('Action items must be an array.');
|
|
205
190
|
|
|
206
191
|
const ids=new Set();
|
|
207
192
|
const items=value.map(function normalizeActionItemRecord(item){
|
|
@@ -213,7 +198,7 @@ export function normalizeConversationActionItems(value=[]){
|
|
|
213
198
|
return normalized;
|
|
214
199
|
});
|
|
215
200
|
|
|
216
|
-
return
|
|
201
|
+
return completeItems(items);
|
|
217
202
|
}
|
|
218
203
|
|
|
219
204
|
export function createConversationActionItem(action,{
|
|
@@ -277,10 +262,6 @@ export function rememberConversationActionItems(current=[],actions=[],{
|
|
|
277
262
|
}
|
|
278
263
|
continue;
|
|
279
264
|
}
|
|
280
|
-
if(items.length>=MAX_ACTION_ITEMS){
|
|
281
|
-
throw invalid(`Action items must contain at most ${MAX_ACTION_ITEMS} records.`);
|
|
282
|
-
}
|
|
283
|
-
|
|
284
265
|
const item=createConversationActionItem(action,{
|
|
285
266
|
id:idFactory(),
|
|
286
267
|
sourceChatId,
|
|
@@ -295,7 +276,7 @@ export function rememberConversationActionItems(current=[],actions=[],{
|
|
|
295
276
|
knownIds.add(item.id);
|
|
296
277
|
}
|
|
297
278
|
|
|
298
|
-
return
|
|
279
|
+
return completeItems(items);
|
|
299
280
|
}
|
|
300
281
|
|
|
301
282
|
export function updateConversationActionItem(current=[],id,patch={}, {
|
|
@@ -341,7 +322,7 @@ export function updateConversationActionItem(current=[],id,patch={}, {
|
|
|
341
322
|
throw invalid(`Unknown action item id: ${targetId}.`);
|
|
342
323
|
}
|
|
343
324
|
|
|
344
|
-
return
|
|
325
|
+
return completeItems(updated);
|
|
345
326
|
}
|
|
346
327
|
|
|
347
328
|
export function removeConversationActionItem(current=[],id){
|
|
@@ -353,11 +334,11 @@ export function removeConversationActionItem(current=[],id){
|
|
|
353
334
|
throw invalid(`Unknown action item id: ${targetId}.`);
|
|
354
335
|
}
|
|
355
336
|
|
|
356
|
-
return
|
|
337
|
+
return completeItems(filtered);
|
|
357
338
|
}
|
|
358
339
|
|
|
359
340
|
export function outstandingConversationActionItems(current=[]){
|
|
360
|
-
return
|
|
341
|
+
return completeItems(
|
|
361
342
|
normalizeConversationActionItems(current).filter(item=>item.status==='open')
|
|
362
343
|
);
|
|
363
344
|
}
|
|
@@ -365,8 +346,7 @@ export function outstandingConversationActionItems(current=[]){
|
|
|
365
346
|
export function selectConversationActionItemsForPresentation(current=[],{
|
|
366
347
|
conversationId,
|
|
367
348
|
now=Date.now(),
|
|
368
|
-
cooldownMs=DEFAULT_PRESENTATION_COOLDOWN_MS
|
|
369
|
-
limit=MAX_PRESENTED_ACTION_ITEMS
|
|
349
|
+
cooldownMs=DEFAULT_PRESENTATION_COOLDOWN_MS
|
|
370
350
|
}={}){
|
|
371
351
|
const activeConversationId=normalizedOptionalId(conversationId,'conversationId');
|
|
372
352
|
if(!activeConversationId){
|
|
@@ -376,10 +356,6 @@ export function selectConversationActionItemsForPresentation(current=[],{
|
|
|
376
356
|
if(!Number.isSafeInteger(cooldownMs)||cooldownMs<0){
|
|
377
357
|
throw invalid('cooldownMs must be a non-negative integer.');
|
|
378
358
|
}
|
|
379
|
-
if(!Number.isSafeInteger(limit)||limit<0||limit>MAX_PRESENTED_ACTION_ITEMS){
|
|
380
|
-
throw invalid(`limit must be between 0 and ${MAX_PRESENTED_ACTION_ITEMS}.`);
|
|
381
|
-
}
|
|
382
|
-
|
|
383
359
|
const selected=outstandingConversationActionItems(current)
|
|
384
360
|
.filter(item=>item.sourceChatId!==activeConversationId)
|
|
385
361
|
.filter(item=>{
|
|
@@ -399,10 +375,9 @@ export function selectConversationActionItemsForPresentation(current=[],{
|
|
|
399
375
|
-(right.lastPresentedAt??right.createdAt)
|
|
400
376
|
||left.createdAt-right.createdAt
|
|
401
377
|
||left.id.localeCompare(right.id);
|
|
402
|
-
})
|
|
403
|
-
.slice(0,limit);
|
|
378
|
+
});
|
|
404
379
|
|
|
405
|
-
return
|
|
380
|
+
return completeItems(selected);
|
|
406
381
|
}
|
|
407
382
|
|
|
408
383
|
export function markConversationActionItemsPresented(current=[],ids=[],{
|
|
@@ -415,9 +390,7 @@ export function markConversationActionItemsPresented(current=[],ids=[],{
|
|
|
415
390
|
throw invalid('conversationId is required.');
|
|
416
391
|
}
|
|
417
392
|
const timestamp=normalizedNow(now);
|
|
418
|
-
if(!Array.isArray(ids)
|
|
419
|
-
throw invalid(`Presented ids must contain at most ${MAX_PRESENTED_ACTION_ITEMS} entry.`);
|
|
420
|
-
}
|
|
393
|
+
if(!Array.isArray(ids))throw invalid('Presented ids must be an array.');
|
|
421
394
|
const requested=new Set(ids.map(id=>normalizedId(id)));
|
|
422
395
|
|
|
423
396
|
let matched=0;
|
|
@@ -444,7 +417,7 @@ export function markConversationActionItemsPresented(current=[],ids=[],{
|
|
|
444
417
|
throw invalid('A presented action item id is unknown.');
|
|
445
418
|
}
|
|
446
419
|
|
|
447
|
-
return
|
|
420
|
+
return completeItems(updated);
|
|
448
421
|
}
|
|
449
422
|
|
|
450
423
|
export function conversationActionItemsInstruction(selected=[],{
|
|
@@ -456,15 +429,12 @@ export function conversationActionItemsInstruction(selected=[],{
|
|
|
456
429
|
}
|
|
457
430
|
|
|
458
431
|
const items=normalizeConversationActionItems(selected);
|
|
459
|
-
if(items.length>MAX_PRESENTED_ACTION_ITEMS){
|
|
460
|
-
throw invalid(`At most ${MAX_PRESENTED_ACTION_ITEMS} action item may be presented.`);
|
|
461
|
-
}
|
|
462
432
|
if(!items.length){
|
|
463
433
|
return '## Remembered follow-ups\nThere is no saved follow-up selected for this conversation. Populate remembered_actions alongside final_message only for a commitment or optional homework the user explicitly agreed to carry forward. Do not turn ordinary suggestions into saved obligations.';
|
|
464
434
|
}
|
|
465
435
|
|
|
466
436
|
const payload=items.map(item=>({id:item.id,text:item.text,basis:item.basis}));
|
|
467
|
-
return `## Remembered follow-up check-in\nThe following JSON
|
|
437
|
+
return `## Remembered follow-up check-in\nThe following JSON contains saved profile data: ${JSON.stringify(payload)}\nThe application displays these saved follow-ups after the first successful assistant response. Do not repeat, paraphrase, or proactively mention the check-ins yourself. After they have been displayed, call \`${completionToolName}\` as the sole tool call only if the user explicitly confirms completion. Its arguments must include a nonempty message containing the exact ordinary user-facing confirmation text to display while the application handles the structural call. The application asks the user for final local confirmation before changing Profile data. Do not assume failure or incompletion. Never invent a due date, reminder, notification, or external delivery.`;
|
|
468
438
|
}
|
|
469
439
|
|
|
470
440
|
export function formatConversationActionItemCheckIn(value){
|
|
@@ -481,8 +451,5 @@ export function formatConversationActionItemCheckIn(value){
|
|
|
481
451
|
export {
|
|
482
452
|
ACTION_ITEM_BASES as CONVERSATION_ACTION_ITEM_BASES,
|
|
483
453
|
ACTION_ITEM_STATUSES as CONVERSATION_ACTION_ITEM_STATUSES,
|
|
484
|
-
DEFAULT_PRESENTATION_COOLDOWN_MS as CONVERSATION_ACTION_ITEM_PRESENTATION_COOLDOWN_MS
|
|
485
|
-
MAX_ACTION_ITEMS as MAX_CONVERSATION_ACTION_ITEMS,
|
|
486
|
-
MAX_ACTION_ITEM_CHARACTERS as MAX_CONVERSATION_ACTION_ITEM_CHARACTERS,
|
|
487
|
-
MAX_REMEMBERED_ACTIONS as MAX_CONVERSATION_REMEMBERED_ACTIONS
|
|
454
|
+
DEFAULT_PRESENTATION_COOLDOWN_MS as CONVERSATION_ACTION_ITEM_PRESENTATION_COOLDOWN_MS
|
|
488
455
|
};
|
|
@@ -1,18 +1,15 @@
|
|
|
1
1
|
import {
|
|
2
2
|
CONVERSATION_ACTION_ITEM_BASES,
|
|
3
|
-
MAX_CONVERSATION_ACTION_ITEM_CHARACTERS,
|
|
4
|
-
MAX_CONVERSATION_REMEMBERED_ACTIONS,
|
|
5
3
|
normalizeRememberedConversationActions
|
|
6
4
|
} from './ConversationActionItems.js?v=2';
|
|
7
5
|
|
|
8
6
|
const DEFAULT_TOOL_NAME='prepare_conversation_closing_report';
|
|
9
|
-
const TOOL_NAME_PATTERN=/^[a-z][a-z0-9_]
|
|
10
|
-
const
|
|
11
|
-
|
|
12
|
-
const REPORT_FIELDS=Object.freeze(new Set([
|
|
7
|
+
const TOOL_NAME_PATTERN=/^[a-z][a-z0-9_]*$/;
|
|
8
|
+
const REPORT_FIELDS=new Set([
|
|
9
|
+
'message',
|
|
13
10
|
'final_message',
|
|
14
11
|
'remembered_actions'
|
|
15
|
-
])
|
|
12
|
+
]);
|
|
16
13
|
|
|
17
14
|
function invalid(message){
|
|
18
15
|
const error=new TypeError(message);
|
|
@@ -29,7 +26,7 @@ function isPlainRecord(value){
|
|
|
29
26
|
return prototype===Object.prototype||prototype===null;
|
|
30
27
|
}
|
|
31
28
|
|
|
32
|
-
function
|
|
29
|
+
function normalizedText(value,label,{required=false}={}){
|
|
33
30
|
if(value===undefined||value===null||value===''){
|
|
34
31
|
if(required){
|
|
35
32
|
throw invalid(`${label} is required.`);
|
|
@@ -40,45 +37,26 @@ function boundedText(value,label,maximum,{required=false}={}){
|
|
|
40
37
|
throw invalid(`${label} must be a string.`);
|
|
41
38
|
}
|
|
42
39
|
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
if(!normalized&&required){
|
|
40
|
+
if(!value.trim()&&required){
|
|
46
41
|
throw invalid(`${label} is required.`);
|
|
47
42
|
}
|
|
48
|
-
|
|
49
|
-
throw invalid(`${label} exceeds ${maximum} characters.`);
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
return normalized;
|
|
43
|
+
return value;
|
|
53
44
|
}
|
|
54
45
|
|
|
55
|
-
function
|
|
46
|
+
function normalizedFinalMessage(value){
|
|
56
47
|
if(typeof value!=='string'){
|
|
57
48
|
throw invalid('final_message must be a string.');
|
|
58
49
|
}
|
|
59
50
|
|
|
60
|
-
|
|
61
|
-
.replace(/\r\n?/g,'\n')
|
|
62
|
-
.replace(/[\u0000-\u0009\u000b\u000c\u000e-\u001f\u007f]+/g,' ')
|
|
63
|
-
.trim();
|
|
64
|
-
|
|
65
|
-
if(!normalized){
|
|
51
|
+
if(!value.trim()){
|
|
66
52
|
throw invalid('final_message is required.');
|
|
67
53
|
}
|
|
68
|
-
|
|
69
|
-
throw invalid(
|
|
70
|
-
`final_message exceeds ${MAX_FINAL_MESSAGE_CHARACTERS} characters.`
|
|
71
|
-
);
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
return normalized;
|
|
54
|
+
return value;
|
|
75
55
|
}
|
|
76
56
|
|
|
77
57
|
function parseArguments(value){
|
|
78
58
|
if(typeof value==='string'){
|
|
79
|
-
if(!value
|
|
80
|
-
throw invalid('The closing-report arguments exceed the allowed size.');
|
|
81
|
-
}
|
|
59
|
+
if(!value)throw invalid('The closing-report arguments are required.');
|
|
82
60
|
|
|
83
61
|
try{
|
|
84
62
|
value=JSON.parse(value);
|
|
@@ -100,16 +78,8 @@ function escapeRawHTML(value){
|
|
|
100
78
|
.replaceAll('>','>');
|
|
101
79
|
}
|
|
102
80
|
|
|
103
|
-
function
|
|
104
|
-
|
|
105
|
-
return value;
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
for(const child of Object.values(value)){
|
|
109
|
-
deepFreeze(child);
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
return Object.freeze(value);
|
|
81
|
+
function completeValue(value){
|
|
82
|
+
return value;
|
|
113
83
|
}
|
|
114
84
|
|
|
115
85
|
/**
|
|
@@ -119,33 +89,36 @@ function deepFreeze(value){
|
|
|
119
89
|
*/
|
|
120
90
|
export function createConversationClosingReportTool({
|
|
121
91
|
name=DEFAULT_TOOL_NAME,
|
|
122
|
-
description='Prepare a
|
|
92
|
+
description='Prepare a complete closing report when the user clearly ends a conversation.'
|
|
123
93
|
}={}){
|
|
124
94
|
if(typeof name!=='string'||!TOOL_NAME_PATTERN.test(name)){
|
|
125
95
|
throw invalid('The closing-report tool name is invalid.');
|
|
126
96
|
}
|
|
127
|
-
if(typeof description!=='string'||!description.trim()
|
|
97
|
+
if(typeof description!=='string'||!description.trim()){
|
|
128
98
|
throw invalid('The closing-report tool description is invalid.');
|
|
129
99
|
}
|
|
130
100
|
|
|
131
|
-
return
|
|
101
|
+
return completeValue({
|
|
132
102
|
type:'function',
|
|
133
103
|
function:{
|
|
134
104
|
name,
|
|
135
|
-
description
|
|
105
|
+
description,
|
|
136
106
|
parameters:{
|
|
137
107
|
type:'object',
|
|
138
108
|
additionalProperties:false,
|
|
139
109
|
properties:{
|
|
110
|
+
message:{
|
|
111
|
+
type:'string',
|
|
112
|
+
minLength:1,
|
|
113
|
+
description:'Brief plain-language progress text shown to the user while the application accepts and renders the closing report. Do not put the complete closeout here or claim that later actions were saved.'
|
|
114
|
+
},
|
|
140
115
|
final_message:{
|
|
141
116
|
type:'string',
|
|
142
117
|
minLength:1,
|
|
143
|
-
maxLength:MAX_FINAL_MESSAGE_CHARACTERS,
|
|
144
118
|
description:'The complete final response shown to the user to close the conversation. When useful and grounded in the conversation, include a concise summary of what was accomplished, concrete progress or takeaways, and optional next steps or homework. Omit any of those elements when they would be inappropriate, unsafe, unwanted, or unsupported. Present next steps as choices, never obligations. If remembered_actions are included, mention them only as agreed follow-ups and never claim they were saved. End with a calm, warm closing that does not pressure the user or create new obligations.'
|
|
145
119
|
},
|
|
146
120
|
remembered_actions:{
|
|
147
121
|
type:'array',
|
|
148
|
-
maxItems:MAX_CONVERSATION_REMEMBERED_ACTIONS,
|
|
149
122
|
items:{
|
|
150
123
|
type:'object',
|
|
151
124
|
additionalProperties:false,
|
|
@@ -153,7 +126,6 @@ export function createConversationClosingReportTool({
|
|
|
153
126
|
text:{
|
|
154
127
|
type:'string',
|
|
155
128
|
minLength:1,
|
|
156
|
-
maxLength:MAX_CONVERSATION_ACTION_ITEM_CHARACTERS,
|
|
157
129
|
description:'The follow-up the user explicitly agreed to carry into a later conversation.'
|
|
158
130
|
},
|
|
159
131
|
basis:{
|
|
@@ -167,7 +139,7 @@ export function createConversationClosingReportTool({
|
|
|
167
139
|
description:'Optional machine-readable follow-ups for local Profile persistence. Include only commitments or optional homework the user explicitly agreed to carry into a later conversation. Do not include ordinary suggestions from final_message.'
|
|
168
140
|
}
|
|
169
141
|
},
|
|
170
|
-
required:['final_message']
|
|
142
|
+
required:['message','final_message']
|
|
171
143
|
}
|
|
172
144
|
}
|
|
173
145
|
});
|
|
@@ -189,14 +161,13 @@ export function conversationClosingReportInstruction({
|
|
|
189
161
|
throw invalid('The closing-report tool name is invalid.');
|
|
190
162
|
}
|
|
191
163
|
|
|
192
|
-
const trigger=
|
|
164
|
+
const trigger=normalizedText(
|
|
193
165
|
completionTrigger,
|
|
194
166
|
'The conversation completion trigger',
|
|
195
|
-
1000,
|
|
196
167
|
{required:true}
|
|
197
168
|
);
|
|
198
169
|
|
|
199
|
-
return `## Conversation closeout\nWhen ${trigger}, call \`${toolName}\` as the sole tool call and do not also answer in prose. Do not call it after a routine answer, while requested work remains open, or merely because a response is long. Use only facts established in the conversation. Never diagnose, score, judge, or infer personal traits. Put the complete user-facing closeout in final_message. Populate remembered_actions only for a commitment or optional homework the user explicitly agreed to carry into a later conversation; never turn an ordinary suggestion into a saved obligation or claim it was saved. The application separately requests consent before saving a remembered action, displays final_message in the conversation, and reports the save result. The tool does not send anything elsewhere or perform any suggested action.`;
|
|
170
|
+
return `## Conversation closeout\nWhen ${trigger}, call \`${toolName}\` as the sole tool call and do not also answer in prose. Do not call it after a routine answer, while requested work remains open, or merely because a response is long. Use only facts established in the conversation. Never diagnose, score, judge, or infer personal traits. Include a brief nonempty message for the user-facing progress shown while the application accepts and renders the call; do not put the complete closeout there or claim later actions were saved. Put the complete user-facing closeout in final_message. Populate remembered_actions only for a commitment or optional homework the user explicitly agreed to carry into a later conversation; never turn an ordinary suggestion into a saved obligation or claim it was saved. The application separately requests consent before saving a remembered action, displays final_message in the conversation, and reports the save result. The tool does not send anything elsewhere or perform any suggested action.`;
|
|
200
171
|
}
|
|
201
172
|
|
|
202
173
|
export function normalizeConversationClosingReport(value){
|
|
@@ -207,8 +178,9 @@ export function normalizeConversationClosingReport(value){
|
|
|
207
178
|
throw invalid(`Unexpected closing-report field: ${unexpectedField}.`);
|
|
208
179
|
}
|
|
209
180
|
|
|
210
|
-
return
|
|
211
|
-
|
|
181
|
+
return completeValue({
|
|
182
|
+
message:normalizedText(source.message,'message',{required:true}),
|
|
183
|
+
finalMessage:normalizedFinalMessage(source.final_message),
|
|
212
184
|
rememberedActions:normalizeRememberedConversationActions(
|
|
213
185
|
source.remembered_actions||[]
|
|
214
186
|
)
|
|
@@ -227,17 +199,17 @@ export function classifyConversationClosingReportCalls(calls={}, {
|
|
|
227
199
|
throw invalid('The closing-report tool name is invalid.');
|
|
228
200
|
}
|
|
229
201
|
if(!isPlainRecord(calls)){
|
|
230
|
-
return
|
|
202
|
+
return {present:false,accepted:false,report:null,error:invalid('Tool calls must be an object.')};
|
|
231
203
|
}
|
|
232
204
|
|
|
233
205
|
const names=Object.keys(calls);
|
|
234
206
|
const present=Object.hasOwn(calls,toolName);
|
|
235
207
|
|
|
236
208
|
if(!present){
|
|
237
|
-
return
|
|
209
|
+
return {present:false,accepted:false,report:null,error:null};
|
|
238
210
|
}
|
|
239
211
|
if(names.length!==1){
|
|
240
|
-
return
|
|
212
|
+
return completeValue({
|
|
241
213
|
present:true,
|
|
242
214
|
accepted:false,
|
|
243
215
|
report:null,
|
|
@@ -246,20 +218,21 @@ export function classifyConversationClosingReportCalls(calls={}, {
|
|
|
246
218
|
}
|
|
247
219
|
|
|
248
220
|
try{
|
|
249
|
-
return
|
|
221
|
+
return completeValue({
|
|
250
222
|
present:true,
|
|
251
223
|
accepted:true,
|
|
252
224
|
report:normalizeConversationClosingReport(calls[toolName]),
|
|
253
225
|
error:null
|
|
254
226
|
});
|
|
255
227
|
}catch(error){
|
|
256
|
-
return
|
|
228
|
+
return {present:true,accepted:false,report:null,error};
|
|
257
229
|
}
|
|
258
230
|
}
|
|
259
231
|
|
|
260
232
|
export function formatConversationClosingReport(value){
|
|
261
233
|
const normalizedInput=isPlainRecord(value)&&value.finalMessage!==undefined
|
|
262
234
|
?{
|
|
235
|
+
message:value.message,
|
|
263
236
|
final_message:value.finalMessage,
|
|
264
237
|
remembered_actions:value.rememberedActions,
|
|
265
238
|
}
|
|
@@ -25,7 +25,7 @@ export const CONVERSATION_TIMEBOX_REASONS=Object.freeze({
|
|
|
25
25
|
started:'conversation-timebox-started'
|
|
26
26
|
});
|
|
27
27
|
|
|
28
|
-
const CONVERSATION_TIMEBOX_OPENING_INSTRUCTION=`Before any other intake question, ask exactly one timing question: "Do you have a limited amount of time to talk right now?" Ask no other question in that opening reply, and wait for the answer before continuing the app's normal intake. The elapsed conversation timer is already visible. A time limit applies only to this conversation. Never infer, round, or invent a duration. The conversation_timebox tool is available on every turn. When the user explicitly sets a duration, call it with action "set" and convert the exact stated duration to whole milliseconds. When the user explicitly adds time, call it with action "adjust" and convert the exact added duration to whole milliseconds. When the user explicitly says there is no limit, asks to remove it, or chooses to continue after a due check without setting another duration, call it with action "clear". A duration is always sent as duration_milliseconds; for example, 75 seconds is 75000 and 10 minutes is 600000. If the duration is vague or ranged, ask one focused question and do not call the tool yet. The timebox tool must be the sole tool call for its response. Adapt the pace and prioritize the most important next action within an active limit without skipping safety. A message that the agreed time check is due is a check-in, not a request to end: ask whether the user wants to end now or continue, and do not assume their choice.`;
|
|
28
|
+
const CONVERSATION_TIMEBOX_OPENING_INSTRUCTION=`Before any other intake question, ask exactly one timing question: "Do you have a limited amount of time to talk right now?" Ask no other question in that opening reply, and wait for the answer before continuing the app's normal intake. The elapsed conversation timer is already visible. A time limit applies only to this conversation. Never infer, round, or invent a duration. The conversation_timebox tool is available on every turn. When the user explicitly sets a duration, call it with action "set" and convert the exact stated duration to whole milliseconds. When the user explicitly adds time, call it with action "adjust" and convert the exact added duration to whole milliseconds. When the user explicitly says there is no limit, asks to remove it, or chooses to continue after a due check without setting another duration, call it with action "clear". Every call must include a nonempty message containing the brief plain-language progress or next-step text shown to the user; do not claim the time check changed before the tool result confirms it. A duration is always sent as duration_milliseconds; for example, 75 seconds is 75000 and 10 minutes is 600000. If the duration is vague or ranged, ask one focused question and do not call the tool yet. The timebox tool must be the sole tool call for its response. Adapt the pace and prioritize the most important next action within an active limit without skipping safety. A message that the agreed time check is due is a check-in, not a request to end: ask whether the user wants to end now or continue, and do not assume their choice.`;
|
|
29
29
|
|
|
30
30
|
const CONVERSATION_TIMEBOX_LIMIT_MESSAGE='The agreed conversation time check is due. Please ask whether I want to end now or continue, and do not assume my choice or set another limit unless I explicitly state one.';
|
|
31
31
|
|
|
@@ -43,13 +43,18 @@ const conversationTimeboxTool=Object.freeze({
|
|
|
43
43
|
enum:Object.freeze(['set','adjust','clear']),
|
|
44
44
|
description:'Set a new deadline, add time to the active deadline, or clear the deadline.'
|
|
45
45
|
}),
|
|
46
|
+
message:{
|
|
47
|
+
type:'string',
|
|
48
|
+
minLength:1,
|
|
49
|
+
description:'Brief plain-language progress or next-step text shown to the user for this tool call. Do not claim the time check changed before the tool result confirms it.'
|
|
50
|
+
},
|
|
46
51
|
duration_milliseconds:Object.freeze({
|
|
47
52
|
type:'integer',
|
|
48
53
|
minimum:0,
|
|
49
54
|
description:'The exact positive whole-millisecond duration. Required for "set" and "adjust"; ignored for "clear". Examples: 75 seconds = 75000; 10 minutes = 600000.'
|
|
50
55
|
})
|
|
51
56
|
}),
|
|
52
|
-
required:Object.freeze(['action'])
|
|
57
|
+
required:Object.freeze(['action','message'])
|
|
53
58
|
})
|
|
54
59
|
})
|
|
55
60
|
});
|
|
@@ -84,7 +89,7 @@ function parseToolArguments(value){
|
|
|
84
89
|
|
|
85
90
|
function normalizeConversationTimeboxCommand(value){
|
|
86
91
|
const input=parseToolArguments(value);
|
|
87
|
-
const allowed=new Set(['action','duration_milliseconds']);
|
|
92
|
+
const allowed=new Set(['action','duration_milliseconds','message']);
|
|
88
93
|
const unsupported=Object.keys(input).find(function findUnsupportedTimeboxField(key){
|
|
89
94
|
return !allowed.has(key);
|
|
90
95
|
});
|
|
@@ -95,15 +100,19 @@ function normalizeConversationTimeboxCommand(value){
|
|
|
95
100
|
if(!['set','adjust','clear'].includes(input.action)){
|
|
96
101
|
throw new TypeError('Conversation timebox action must be "set", "adjust", or "clear".');
|
|
97
102
|
}
|
|
103
|
+
if(typeof input.message!=='string'||!input.message.trim()){
|
|
104
|
+
throw new TypeError('Conversation timebox message must contain user-facing text.');
|
|
105
|
+
}
|
|
98
106
|
if(input.action==='clear'){
|
|
99
|
-
return Object.freeze({action:'clear'});
|
|
107
|
+
return Object.freeze({action:'clear',message:input.message});
|
|
100
108
|
}
|
|
101
109
|
if(!Number.isSafeInteger(input.duration_milliseconds)||input.duration_milliseconds<=0){
|
|
102
110
|
throw new TypeError('duration_milliseconds must be an explicit positive whole number.');
|
|
103
111
|
}
|
|
104
112
|
return Object.freeze({
|
|
105
113
|
action:input.action,
|
|
106
|
-
durationMilliseconds:input.duration_milliseconds
|
|
114
|
+
durationMilliseconds:input.duration_milliseconds,
|
|
115
|
+
message:input.message
|
|
107
116
|
});
|
|
108
117
|
}
|
|
109
118
|
|
|
@@ -406,18 +415,21 @@ class ConversationTimebox{
|
|
|
406
415
|
|
|
407
416
|
applyCommand(value){
|
|
408
417
|
const command=normalizeConversationTimeboxCommand(value);
|
|
418
|
+
let state;
|
|
409
419
|
if(command.action==='clear'){
|
|
410
|
-
|
|
420
|
+
state=this.clearLimit({source:'tool'});
|
|
421
|
+
}else{
|
|
422
|
+
state=command.action==='adjust'
|
|
423
|
+
?this.adjustLimitMilliseconds(
|
|
424
|
+
command.durationMilliseconds,
|
|
425
|
+
{source:'tool'}
|
|
426
|
+
)
|
|
427
|
+
:this.setLimitMilliseconds(
|
|
428
|
+
command.durationMilliseconds,
|
|
429
|
+
{source:'tool'}
|
|
430
|
+
);
|
|
411
431
|
}
|
|
412
|
-
return command.
|
|
413
|
-
?this.adjustLimitMilliseconds(
|
|
414
|
-
command.durationMilliseconds,
|
|
415
|
-
{source:'tool'}
|
|
416
|
-
)
|
|
417
|
-
:this.setLimitMilliseconds(
|
|
418
|
-
command.durationMilliseconds,
|
|
419
|
-
{source:'tool'}
|
|
420
|
-
);
|
|
432
|
+
return {...state,message:command.message};
|
|
421
433
|
}
|
|
422
434
|
|
|
423
435
|
subscribe(listener,options={}){
|