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,527 @@
1
+ const CONVERSATION_TIMEBOX_TOOL_NAME='conversation_timebox';
2
+ const CONVERSATION_TIMEBOX_TICK_MS=1000;
3
+ const MAX_DATE_MILLISECONDS=8_640_000_000_000_000;
4
+
5
+ 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.`;
6
+
7
+ 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.';
8
+
9
+ const conversationTimeboxTool=Object.freeze({
10
+ type:'function',
11
+ function:Object.freeze({
12
+ name:CONVERSATION_TIMEBOX_TOOL_NAME,
13
+ description:'Control the current conversation time check whenever the user explicitly sets, adds, removes, or continues past it. Use this as the sole tool call. Use action "set" to replace the deadline relative to now, "adjust" to add time to the active deadline, and "clear" to remove the deadline, including continuing after a due check without a new duration. Convert exact stated units to whole milliseconds; never infer, round, or choose a default.',
14
+ parameters:Object.freeze({
15
+ type:'object',
16
+ additionalProperties:false,
17
+ properties:Object.freeze({
18
+ action:Object.freeze({
19
+ type:'string',
20
+ enum:Object.freeze(['set','adjust','clear']),
21
+ description:'Set a new deadline, add time to the active deadline, or clear the deadline.'
22
+ }),
23
+ duration_milliseconds:Object.freeze({
24
+ type:'integer',
25
+ minimum:0,
26
+ description:'The exact positive whole-millisecond duration. Required for "set" and "adjust"; ignored for "clear". Examples: 75 seconds = 75000; 10 minutes = 600000.'
27
+ })
28
+ }),
29
+ required:Object.freeze(['action'])
30
+ })
31
+ })
32
+ });
33
+
34
+ function isPlainRecord(value){
35
+ return Boolean(value)
36
+ &&typeof value==='object'
37
+ &&!Array.isArray(value)
38
+ &&Object.getPrototypeOf(value)===Object.prototype;
39
+ }
40
+
41
+ function parseToolArguments(value){
42
+ if(isPlainRecord(value)){
43
+ return value;
44
+ }
45
+ if(typeof value!=='string'){
46
+ throw new TypeError('The conversation timebox tool requires JSON object arguments.');
47
+ }
48
+ try{
49
+ const parsed=JSON.parse(value);
50
+ if(!isPlainRecord(parsed)){
51
+ throw new TypeError('The conversation timebox tool requires a JSON object.');
52
+ }
53
+ return parsed;
54
+ }catch(error){
55
+ if(error instanceof TypeError){
56
+ throw error;
57
+ }
58
+ throw new TypeError('The conversation timebox tool returned invalid JSON arguments.');
59
+ }
60
+ }
61
+
62
+ function normalizeConversationTimeboxCommand(value){
63
+ const input=parseToolArguments(value);
64
+ const allowed=new Set(['action','duration_milliseconds']);
65
+ const unsupported=Object.keys(input).find(function findUnsupportedTimeboxField(key){
66
+ return !allowed.has(key);
67
+ });
68
+
69
+ if(unsupported){
70
+ throw new TypeError(`Unsupported conversation timebox field: ${unsupported}`);
71
+ }
72
+ if(!['set','adjust','clear'].includes(input.action)){
73
+ throw new TypeError('Conversation timebox action must be "set", "adjust", or "clear".');
74
+ }
75
+ if(input.action==='clear'){
76
+ return Object.freeze({action:'clear'});
77
+ }
78
+ if(!Number.isSafeInteger(input.duration_milliseconds)||input.duration_milliseconds<=0){
79
+ throw new TypeError('duration_milliseconds must be an explicit positive whole number.');
80
+ }
81
+ return Object.freeze({
82
+ action:input.action,
83
+ durationMilliseconds:input.duration_milliseconds
84
+ });
85
+ }
86
+
87
+ function appendConversationTimeboxOpeningInstruction(message=''){
88
+ if(typeof message!=='string'){
89
+ throw new TypeError('The opening message must be a string.');
90
+ }
91
+ const base=message.trim();
92
+ return base
93
+ ?`${base}\n\n${CONVERSATION_TIMEBOX_OPENING_INSTRUCTION}`
94
+ :CONVERSATION_TIMEBOX_OPENING_INSTRUCTION;
95
+ }
96
+
97
+ function formatConversationElapsed(milliseconds=0){
98
+ if(!Number.isFinite(milliseconds)||milliseconds<0){
99
+ throw new TypeError('Elapsed time must be a non-negative finite number.');
100
+ }
101
+ const totalSeconds=Math.floor(milliseconds/1000);
102
+ const hours=Math.floor(totalSeconds/3600);
103
+ const minutes=Math.floor((totalSeconds%3600)/60);
104
+ const seconds=totalSeconds%60;
105
+ return hours>0
106
+ ?`${String(hours).padStart(2,'0')}:${String(minutes).padStart(2,'0')}:${String(seconds).padStart(2,'0')}`
107
+ :`${String(minutes).padStart(2,'0')}:${String(seconds).padStart(2,'0')}`;
108
+ }
109
+
110
+ function createConversationTimeboxControlMessage(action,milliseconds){
111
+ if(action==='clear'){
112
+ return 'I used the conversation controls to confirm that I do not have a time limit right now. The elapsed timer should keep running. The limit is already cleared; do not infer or set another limit unless I explicitly state one.';
113
+ }
114
+ if(
115
+ !['set','adjust'].includes(action)
116
+ ||!Number.isSafeInteger(milliseconds)
117
+ ||milliseconds<=0
118
+ ){
119
+ throw new TypeError('A timebox control message requires an explicit positive whole-millisecond duration.');
120
+ }
121
+ const duration=formatConversationElapsed(milliseconds);
122
+ return action==='adjust'
123
+ ?`I used the conversation controls to add ${duration} to the active check-in. The timer is already adjusted. Please adapt our pace and priorities to it.`
124
+ :`I used the conversation controls to set a check-in ${duration} from now. The timer is already set. Please adapt our pace and priorities to it, and do not reset it unless I explicitly revise it.`;
125
+ }
126
+
127
+ function conversationTimeboxSubmissionKey(context={}){
128
+ if(!isPlainRecord(context)||context.synthetic!==true){
129
+ return '';
130
+ }
131
+ const source=String(context.source||'');
132
+ const revision=context.timeboxRevision;
133
+ if(
134
+ !source.startsWith('conversation-timebox')
135
+ ||!Number.isSafeInteger(revision)
136
+ ||revision<1
137
+ ){
138
+ return '';
139
+ }
140
+ return `${source}:${revision}`;
141
+ }
142
+
143
+ function reportConversationTimeboxListenerError(error){
144
+ console.error('A conversation timebox listener failed.',error);
145
+ }
146
+
147
+ function defaultClock(){
148
+ return Date.now();
149
+ }
150
+
151
+ function defaultSchedule(callback,delay){
152
+ return globalThis.setInterval(callback,delay);
153
+ }
154
+
155
+ function defaultCancel(handle){
156
+ globalThis.clearInterval(handle);
157
+ }
158
+
159
+ class ConversationSubmissionBarrier{
160
+ #pending=new Set();
161
+
162
+ get pendingCount(){
163
+ return this.#pending.size;
164
+ }
165
+
166
+ track(result){
167
+ const pending=Promise.resolve(result);
168
+ this.#pending.add(pending);
169
+ const removePending=function removeTrackedConversationSubmission(){
170
+ this.#pending.delete(pending);
171
+ }.bind(this);
172
+ void pending.then(removePending,removePending);
173
+ return pending;
174
+ }
175
+
176
+ async waitForIdle(){
177
+ while(this.#pending.size){
178
+ await Promise.allSettled([...this.#pending]);
179
+ }
180
+ }
181
+ }
182
+
183
+ function requireConversationTimeboxDelivery(result){
184
+ if(result===false){
185
+ throw new Error('The host did not complete the conversation timebox message.');
186
+ }
187
+ return result;
188
+ }
189
+
190
+ class ConversationTimebox{
191
+ #cancel;
192
+ #clock;
193
+ #disposed=false;
194
+ #dueAtMs=null;
195
+ #dueRevision=null;
196
+ #durationMilliseconds=null;
197
+ #intervalHandle=null;
198
+ #listeners=new Set();
199
+ #nowMs=null;
200
+ #onListenerError;
201
+ #revision=0;
202
+ #schedule;
203
+ #setAtMs=null;
204
+ #startedAtMs=null;
205
+ #tickMs;
206
+
207
+ constructor(options={}){
208
+ if(!isPlainRecord(options)){
209
+ throw new TypeError('Conversation timebox options must be a plain object.');
210
+ }
211
+ const allowed=new Set(['cancel','clock','onListenerError','schedule','tickMs']);
212
+ const unsupported=Object.keys(options).find(function findUnsupportedTimeboxOption(key){
213
+ return !allowed.has(key);
214
+ });
215
+ if(unsupported){
216
+ throw new TypeError(`Unsupported conversation timebox option: ${unsupported}`);
217
+ }
218
+
219
+ this.#clock=options.clock??defaultClock;
220
+ this.#schedule=options.schedule??defaultSchedule;
221
+ this.#cancel=options.cancel??defaultCancel;
222
+ this.#onListenerError=options.onListenerError??reportConversationTimeboxListenerError;
223
+ this.#tickMs=options.tickMs??CONVERSATION_TIMEBOX_TICK_MS;
224
+
225
+ for(const [name,value] of [
226
+ ['clock',this.#clock],
227
+ ['schedule',this.#schedule],
228
+ ['cancel',this.#cancel],
229
+ ['onListenerError',this.#onListenerError]
230
+ ]){
231
+ if(typeof value!=='function'){
232
+ throw new TypeError(`${name} must be a function.`);
233
+ }
234
+ }
235
+ if(!Number.isSafeInteger(this.#tickMs)||this.#tickMs<100||this.#tickMs>60_000){
236
+ throw new RangeError('tickMs must be an integer between 100 and 60000.');
237
+ }
238
+ }
239
+
240
+ get limitReachedMessage(){
241
+ return CONVERSATION_TIMEBOX_LIMIT_MESSAGE;
242
+ }
243
+
244
+ controlMessage(action,milliseconds){
245
+ return createConversationTimeboxControlMessage(action,milliseconds);
246
+ }
247
+
248
+ start(){
249
+ this.#assertActive();
250
+ if(this.#startedAtMs!==null){
251
+ return this.snapshot();
252
+ }
253
+ const now=this.#readClock();
254
+ this.#startAt(now,true);
255
+ return this.snapshot();
256
+ }
257
+
258
+ setLimitMilliseconds(milliseconds,{source='user'}={}){
259
+ this.#assertActive();
260
+ if(!Number.isSafeInteger(milliseconds)||milliseconds<=0){
261
+ throw new TypeError('Conversation time must be an explicit positive whole number of milliseconds.');
262
+ }
263
+ if(typeof source!=='string'||!source.trim()){
264
+ throw new TypeError('Conversation timebox source must contain text.');
265
+ }
266
+ const now=this.#readClock();
267
+ const dueAtMs=now+milliseconds;
268
+ if(!Number.isSafeInteger(dueAtMs)||dueAtMs>MAX_DATE_MILLISECONDS){
269
+ throw new RangeError('The requested conversation duration exceeds the supported date range.');
270
+ }
271
+ if(this.#startedAtMs===null){
272
+ this.#startAt(now,false);
273
+ }
274
+
275
+ this.#durationMilliseconds=milliseconds;
276
+ this.#setAtMs=now;
277
+ this.#dueAtMs=dueAtMs;
278
+ this.#dueRevision=null;
279
+ this.#revision++;
280
+ this.#nowMs=now;
281
+ this.#notify('change');
282
+ return this.snapshot();
283
+ }
284
+
285
+ adjustLimitMilliseconds(milliseconds,{source='user'}={}){
286
+ this.#assertActive();
287
+ if(!Number.isSafeInteger(milliseconds)||milliseconds<=0){
288
+ throw new TypeError('Conversation adjustment must be an explicit positive whole number of milliseconds.');
289
+ }
290
+ if(typeof source!=='string'||!source.trim()){
291
+ throw new TypeError('Conversation timebox source must contain text.');
292
+ }
293
+ if(this.#dueAtMs===null){
294
+ throw new Error('An active conversation time check is required before adding time.');
295
+ }
296
+
297
+ const now=this.#readClock();
298
+ const dueAtMs=Math.max(now,this.#dueAtMs)+milliseconds;
299
+ if(!Number.isSafeInteger(dueAtMs)||dueAtMs>MAX_DATE_MILLISECONDS){
300
+ throw new RangeError('The requested conversation adjustment exceeds the supported date range.');
301
+ }
302
+
303
+ this.#durationMilliseconds=dueAtMs-now;
304
+ this.#setAtMs=now;
305
+ this.#dueAtMs=dueAtMs;
306
+ this.#dueRevision=null;
307
+ this.#revision++;
308
+ this.#nowMs=now;
309
+ this.#notify('change');
310
+ return this.snapshot();
311
+ }
312
+
313
+ clearLimit({source='user'}={}){
314
+ this.#assertActive();
315
+ if(typeof source!=='string'||!source.trim()){
316
+ throw new TypeError('Conversation timebox source must contain text.');
317
+ }
318
+ if(this.#startedAtMs===null){
319
+ this.start();
320
+ }
321
+ this.#durationMilliseconds=null;
322
+ this.#setAtMs=null;
323
+ this.#dueAtMs=null;
324
+ this.#dueRevision=null;
325
+ this.#revision++;
326
+ this.#nowMs=this.#readClock();
327
+ this.#notify('change');
328
+ return this.snapshot();
329
+ }
330
+
331
+ applyCommand(value){
332
+ const command=normalizeConversationTimeboxCommand(value);
333
+ if(command.action==='clear'){
334
+ return this.clearLimit({source:'tool'});
335
+ }
336
+ return command.action==='adjust'
337
+ ?this.adjustLimitMilliseconds(
338
+ command.durationMilliseconds,
339
+ {source:'tool'}
340
+ )
341
+ :this.setLimitMilliseconds(
342
+ command.durationMilliseconds,
343
+ {source:'tool'}
344
+ );
345
+ }
346
+
347
+ subscribe(listener){
348
+ if(typeof listener!=='function'){
349
+ throw new TypeError('Conversation timebox listener must be a function.');
350
+ }
351
+ this.#listeners.add(listener);
352
+ listener(
353
+ this.snapshot(),
354
+ Object.freeze({type:'snapshot',revision:this.#revision})
355
+ );
356
+ const listeners=this.#listeners;
357
+ return function unsubscribeConversationTimeboxListener(){
358
+ return listeners.delete(listener);
359
+ };
360
+ }
361
+
362
+ snapshot(){
363
+ const now=this.#startedAtMs===null
364
+ ?null
365
+ :Math.max(this.#nowMs??this.#startedAtMs,this.#startedAtMs);
366
+ const elapsedMs=now===null?0:now-this.#startedAtMs;
367
+ const remainingMs=this.#dueAtMs===null||now===null
368
+ ?null
369
+ :Math.max(0,this.#dueAtMs-now);
370
+ const status=this.#disposed
371
+ ?'disposed'
372
+ :this.#startedAtMs===null
373
+ ?'idle'
374
+ :this.#dueRevision===this.#revision&&this.#dueAtMs!==null
375
+ ?'due'
376
+ :this.#dueAtMs!==null
377
+ ?'armed'
378
+ :'unlimited';
379
+
380
+ return Object.freeze({
381
+ status,
382
+ startedAtMs:this.#startedAtMs,
383
+ elapsedMs,
384
+ durationMilliseconds:this.#durationMilliseconds,
385
+ setAtMs:this.#setAtMs,
386
+ dueAtMs:this.#dueAtMs,
387
+ remainingMs,
388
+ revision:this.#revision,
389
+ dueRevision:this.#dueRevision
390
+ });
391
+ }
392
+
393
+ dispose(){
394
+ if(this.#disposed){
395
+ return this.snapshot();
396
+ }
397
+ if(this.#intervalHandle!==null){
398
+ this.#cancel(this.#intervalHandle);
399
+ this.#intervalHandle=null;
400
+ }
401
+ this.#disposed=true;
402
+ this.#notify('change');
403
+ const finalSnapshot=this.snapshot();
404
+ this.#listeners.clear();
405
+ return finalSnapshot;
406
+ }
407
+
408
+ #assertActive(){
409
+ if(this.#disposed){
410
+ throw new Error('The conversation timebox has been disposed.');
411
+ }
412
+ }
413
+
414
+ #readClock(){
415
+ const now=this.#clock();
416
+ if(!Number.isSafeInteger(now)||now<0||now>MAX_DATE_MILLISECONDS){
417
+ throw new TypeError('Conversation timebox clock must return a supported non-negative integer timestamp.');
418
+ }
419
+ return this.#nowMs===null?now:Math.max(now,this.#nowMs);
420
+ }
421
+
422
+ #startAt(now,notify){
423
+ const intervalHandle=this.#schedule(
424
+ this.#tick.bind(this),
425
+ this.#tickMs
426
+ );
427
+ this.#startedAtMs=now;
428
+ this.#nowMs=now;
429
+ this.#intervalHandle=intervalHandle;
430
+ if(notify){
431
+ this.#notify('change');
432
+ }
433
+ }
434
+
435
+ #tick(){
436
+ if(this.#disposed||this.#startedAtMs===null){
437
+ return;
438
+ }
439
+ this.#nowMs=this.#readClock();
440
+ let becameDue=false;
441
+ if(
442
+ this.#dueAtMs!==null
443
+ &&this.#nowMs>=this.#dueAtMs
444
+ &&this.#dueRevision!==this.#revision
445
+ ){
446
+ this.#dueRevision=this.#revision;
447
+ becameDue=true;
448
+ }
449
+ this.#notify(becameDue?'due':'tick');
450
+ }
451
+
452
+ #notify(type='change'){
453
+ const state=this.snapshot();
454
+ const event=Object.freeze({type,revision:this.#revision});
455
+ for(const listener of this.#listeners){
456
+ try{
457
+ listener(state,event);
458
+ }catch(error){
459
+ this.#onListenerError(error);
460
+ }
461
+ }
462
+ }
463
+ }
464
+
465
+ function consumeConversationTimeboxCall(calls,controller){
466
+ if(!isPlainRecord(calls)){
467
+ throw new TypeError('Streamed conversation tool calls must be a plain object.');
468
+ }
469
+ if(!controller||typeof controller.applyCommand!=='function'){
470
+ throw new TypeError('A conversation timebox controller is required.');
471
+ }
472
+ const remainingCalls={...calls};
473
+ if(!Object.hasOwn(remainingCalls,CONVERSATION_TIMEBOX_TOOL_NAME)){
474
+ return Object.freeze({
475
+ handled:false,
476
+ remainingCalls:Object.freeze(remainingCalls),
477
+ result:null
478
+ });
479
+ }
480
+
481
+ const argumentsValue=remainingCalls[CONVERSATION_TIMEBOX_TOOL_NAME];
482
+ delete remainingCalls[CONVERSATION_TIMEBOX_TOOL_NAME];
483
+ if(Object.keys(remainingCalls).length){
484
+ const reason=new TypeError(
485
+ 'The conversation timebox must be the sole tool call in a response.'
486
+ );
487
+ reason.code='CONVERSATION_TIMEBOX_MUST_BE_SOLE_CALL';
488
+ return Object.freeze({
489
+ handled:true,
490
+ remainingCalls:Object.freeze({}),
491
+ result:Object.freeze({status:'rejected',reason})
492
+ });
493
+ }
494
+ try{
495
+ return Object.freeze({
496
+ handled:true,
497
+ remainingCalls:Object.freeze(remainingCalls),
498
+ result:Object.freeze({
499
+ status:'fulfilled',
500
+ value:controller.applyCommand(argumentsValue)
501
+ })
502
+ });
503
+ }catch(reason){
504
+ return Object.freeze({
505
+ handled:true,
506
+ remainingCalls:Object.freeze(remainingCalls),
507
+ result:Object.freeze({status:'rejected',reason})
508
+ });
509
+ }
510
+ }
511
+
512
+ export {
513
+ appendConversationTimeboxOpeningInstruction,
514
+ CONVERSATION_TIMEBOX_LIMIT_MESSAGE,
515
+ CONVERSATION_TIMEBOX_OPENING_INSTRUCTION,
516
+ CONVERSATION_TIMEBOX_TOOL_NAME,
517
+ ConversationSubmissionBarrier,
518
+ consumeConversationTimeboxCall,
519
+ conversationTimeboxTool,
520
+ conversationTimeboxSubmissionKey,
521
+ createConversationTimeboxControlMessage,
522
+ formatConversationElapsed,
523
+ normalizeConversationTimeboxCommand,
524
+ requireConversationTimeboxDelivery
525
+ };
526
+
527
+ export default ConversationTimebox;