@almadar/std 16.239.0 → 16.242.0

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 (167) hide show
  1. package/behaviors/behaviors-registry.json +4814 -553
  2. package/behaviors/lolo/agent/atoms/std-agent-memory.lolo +38 -1
  3. package/behaviors/lolo/agent/atoms/std-agent-session.lolo +1074 -47
  4. package/behaviors/lolo/agent/atoms/std-agent-tool-loop.lolo +8 -7
  5. package/behaviors/lolo/agent/organisms/std-agent-assistant.lolo +56 -37
  6. package/behaviors/lolo/infra/atoms/std-page-open.lolo +59 -0
  7. package/behaviors/lolo/infra/atoms/std-page-search.lolo +134 -0
  8. package/behaviors/lolo/infra/atoms/std-page-treat.lolo +59 -0
  9. package/behaviors/lolo/infra/atoms/std-page-watch.lolo +86 -0
  10. package/behaviors/lolo/ui/core/atoms/std-board.lolo +53 -6
  11. package/behaviors/lolo/ui/core/atoms/std-book-reader.lolo +25 -1
  12. package/behaviors/lolo/ui/core/atoms/std-card-grid.lolo +9 -0
  13. package/behaviors/lolo/ui/core/atoms/std-doc-page.lolo +153 -0
  14. package/behaviors/lolo/ui/core/atoms/std-site-header.lolo +15 -3
  15. package/behaviors/lolo/ui/core/atoms/std-tag-strip.lolo +2 -2
  16. package/behaviors/lolo/ui/core/atoms/std-tag-taxonomy.lolo +7 -7
  17. package/behaviors/lolo/ui/core/atoms/std-version-history.lolo +1 -1
  18. package/behaviors/lolo/ui/core/molecules/ui-header.lolo +5 -0
  19. package/behaviors/lolo/ui/learning/atoms/std-learn-arrangement.lolo +124 -92
  20. package/behaviors/lolo/ui/learning/atoms/std-learn-classify.lolo +114 -55
  21. package/behaviors/lolo/ui/learning/atoms/std-learn-compare.lolo +40 -14
  22. package/behaviors/lolo/ui/learning/atoms/std-learn-coupled-sim.lolo +363 -123
  23. package/behaviors/lolo/ui/learning/atoms/std-learn-derivation.lolo +93 -48
  24. package/behaviors/lolo/ui/learning/atoms/std-learn-diagram.lolo +60 -28
  25. package/behaviors/lolo/ui/learning/atoms/std-learn-doser.lolo +193 -96
  26. package/behaviors/lolo/ui/learning/atoms/std-learn-estimate.lolo +75 -32
  27. package/behaviors/lolo/ui/learning/atoms/std-learn-goal-seek.lolo +142 -86
  28. package/behaviors/lolo/ui/learning/atoms/std-learn-grid-sim.lolo +266 -98
  29. package/behaviors/lolo/ui/learning/atoms/std-learn-hotspot.lolo +103 -65
  30. package/behaviors/lolo/ui/learning/atoms/std-learn-live-sim.lolo +223 -109
  31. package/behaviors/lolo/ui/learning/atoms/std-learn-measure.lolo +232 -64
  32. package/behaviors/lolo/ui/learning/atoms/std-learn-param-explorer.lolo +69 -29
  33. package/behaviors/lolo/ui/learning/atoms/std-learn-perturb.lolo +363 -123
  34. package/behaviors/lolo/ui/learning/atoms/std-learn-predict.lolo +73 -41
  35. package/behaviors/lolo/ui/learning/atoms/std-learn-sample-accumulator.lolo +96 -38
  36. package/behaviors/lolo/ui/learning/atoms/std-learn-sandbox.lolo +65 -45
  37. package/behaviors/lolo/ui/learning/atoms/std-learn-scatter-fit.lolo +243 -117
  38. package/behaviors/lolo/ui/learning/atoms/std-learn-scenario.lolo +76 -28
  39. package/behaviors/lolo/ui/learning/atoms/std-learn-sim-3d.lolo +228 -109
  40. package/behaviors/lolo/ui/learning/atoms/std-learn-speed-drill.lolo +95 -44
  41. package/behaviors/lolo/ui/learning/atoms/std-learn-stepper.lolo +199 -125
  42. package/behaviors/lolo/ui/learning/atoms/std-learn-tour-3d.lolo +92 -48
  43. package/behaviors/lolo/ui/learning/atoms/std-learn-trace-player.lolo +239 -78
  44. package/behaviors/lolo/ui/learning/atoms/std-learn-transport.lolo +147 -38
  45. package/behaviors/lolo/ui/learning/atoms/std-learn-tree.lolo +123 -58
  46. package/behaviors/lolo/ui/learning/atoms/ui-algo-graph-canvas.lolo +22 -0
  47. package/behaviors/lolo/ui/learning/atoms/ui-algorithm-canvas.lolo +26 -0
  48. package/behaviors/lolo/ui/learning/atoms/ui-biology-canvas.lolo +26 -0
  49. package/behaviors/lolo/ui/learning/atoms/ui-chemistry-canvas.lolo +27 -1
  50. package/behaviors/lolo/ui/learning/atoms/ui-learning-canvas.lolo +27 -1
  51. package/behaviors/lolo/ui/learning/atoms/ui-math-canvas.lolo +29 -9
  52. package/behaviors/lolo/ui/learning/atoms/ui-physics-canvas.lolo +26 -0
  53. package/behaviors/lolo-behavior-contracts.json +1 -1
  54. package/behaviors/registry/agent/atoms/std-agent-memory.orb +372 -2
  55. package/behaviors/registry/agent/atoms/std-agent-session.orb +4752 -348
  56. package/behaviors/registry/agent/atoms/std-agent-tool-loop.orb +54 -42
  57. package/behaviors/registry/agent/organisms/std-agent-assistant.orb +356 -122
  58. package/behaviors/registry/factory-signatures.json +1 -1
  59. package/behaviors/registry/infra/atoms/std-page-open.orb +435 -0
  60. package/behaviors/registry/infra/atoms/std-page-search.orb +867 -0
  61. package/behaviors/registry/infra/atoms/std-page-treat.orb +494 -0
  62. package/behaviors/registry/infra/atoms/std-page-watch.orb +554 -0
  63. package/behaviors/registry/ui/core/atoms/std-board.orb +110 -14
  64. package/behaviors/registry/ui/core/atoms/std-book-reader.orb +119 -0
  65. package/behaviors/registry/ui/core/atoms/std-card-grid.orb +61 -0
  66. package/behaviors/registry/ui/core/atoms/std-doc-page.orb +594 -0
  67. package/behaviors/registry/ui/core/atoms/std-site-header.orb +40 -2
  68. package/behaviors/registry/ui/core/atoms/std-tag-strip.orb +2 -0
  69. package/behaviors/registry/ui/core/atoms/std-tag-taxonomy.orb +24 -0
  70. package/behaviors/registry/ui/core/atoms/std-version-history.orb +8 -1
  71. package/behaviors/registry/ui/core/molecules/ui-header.orb +7 -0
  72. package/behaviors/registry/ui/learning/atoms/std-learn-arrangement.orb +903 -369
  73. package/behaviors/registry/ui/learning/atoms/std-learn-classify.orb +1002 -232
  74. package/behaviors/registry/ui/learning/atoms/std-learn-compare.orb +253 -74
  75. package/behaviors/registry/ui/learning/atoms/std-learn-coupled-sim.orb +3624 -921
  76. package/behaviors/registry/ui/learning/atoms/std-learn-derivation.orb +685 -144
  77. package/behaviors/registry/ui/learning/atoms/std-learn-diagram.orb +404 -52
  78. package/behaviors/registry/ui/learning/atoms/std-learn-doser.orb +1787 -210
  79. package/behaviors/registry/ui/learning/atoms/std-learn-estimate.orb +699 -71
  80. package/behaviors/registry/ui/learning/atoms/std-learn-goal-seek.orb +1102 -578
  81. package/behaviors/registry/ui/learning/atoms/std-learn-grid-sim.orb +2760 -872
  82. package/behaviors/registry/ui/learning/atoms/std-learn-hotspot.orb +689 -152
  83. package/behaviors/registry/ui/learning/atoms/std-learn-live-sim.orb +1431 -633
  84. package/behaviors/registry/ui/learning/atoms/std-learn-measure.orb +2485 -512
  85. package/behaviors/registry/ui/learning/atoms/std-learn-param-explorer.orb +501 -197
  86. package/behaviors/registry/ui/learning/atoms/std-learn-perturb.orb +3642 -928
  87. package/behaviors/registry/ui/learning/atoms/std-learn-predict.orb +705 -125
  88. package/behaviors/registry/ui/learning/atoms/std-learn-sample-accumulator.orb +624 -114
  89. package/behaviors/registry/ui/learning/atoms/std-learn-sandbox.orb +405 -153
  90. package/behaviors/registry/ui/learning/atoms/std-learn-scatter-fit.orb +1911 -611
  91. package/behaviors/registry/ui/learning/atoms/std-learn-scenario.orb +436 -128
  92. package/behaviors/registry/ui/learning/atoms/std-learn-sim-3d.orb +2482 -485
  93. package/behaviors/registry/ui/learning/atoms/std-learn-speed-drill.orb +896 -192
  94. package/behaviors/registry/ui/learning/atoms/std-learn-stepper.orb +2001 -759
  95. package/behaviors/registry/ui/learning/atoms/std-learn-tour-3d.orb +734 -228
  96. package/behaviors/registry/ui/learning/atoms/std-learn-trace-player.orb +2501 -764
  97. package/behaviors/registry/ui/learning/atoms/std-learn-transport.orb +1591 -173
  98. package/behaviors/registry/ui/learning/atoms/std-learn-tree.orb +883 -122
  99. package/behaviors/registry/ui/learning/atoms/ui-algo-graph-canvas.orb +176 -0
  100. package/behaviors/registry/ui/learning/atoms/ui-algorithm-canvas.orb +211 -0
  101. package/behaviors/registry/ui/learning/atoms/ui-biology-canvas.orb +211 -0
  102. package/behaviors/registry/ui/learning/atoms/ui-chemistry-canvas.orb +212 -1
  103. package/behaviors/registry/ui/learning/atoms/ui-learning-canvas.orb +212 -1
  104. package/behaviors/registry/ui/learning/atoms/ui-math-canvas.orb +203 -9
  105. package/behaviors/registry/ui/learning/atoms/ui-physics-canvas.orb +211 -0
  106. package/behaviors/signatures.lolo +325 -655
  107. package/canonical-operators.json +35 -1
  108. package/dist/behaviors/exports-reader.js +41980 -35508
  109. package/dist/behaviors/functions/index.d.ts +1063 -140
  110. package/dist/behaviors/functions/index.js +41951 -35506
  111. package/dist/behaviors/index.d.ts +1 -1
  112. package/dist/behaviors/index.js +41981 -35509
  113. package/dist/behaviors/query.js +41980 -35508
  114. package/dist/behaviors-embeddings.hash.json +3 -3
  115. package/dist/behaviors-embeddings.json +47 -27
  116. package/dist/behaviors-registry.json +4814 -553
  117. package/dist/index.d.ts +4 -4
  118. package/dist/index.js +42019 -35509
  119. package/dist/knob-embeddings.hash.json +3 -3
  120. package/dist/knob-embeddings.json +623 -263
  121. package/dist/modules/anim.d.ts +1 -1
  122. package/dist/modules/array.d.ts +1 -1
  123. package/dist/modules/async.d.ts +1 -1
  124. package/dist/modules/behavior.d.ts +1 -1
  125. package/dist/modules/browser.d.ts +1 -1
  126. package/dist/modules/composition.d.ts +1 -1
  127. package/dist/modules/contract.d.ts +1 -1
  128. package/dist/modules/core.d.ts +1 -1
  129. package/dist/modules/core.js +2 -0
  130. package/dist/modules/data.d.ts +1 -1
  131. package/dist/modules/ease.d.ts +1 -1
  132. package/dist/modules/format.d.ts +1 -1
  133. package/dist/modules/geo.d.ts +1 -1
  134. package/dist/modules/graph.d.ts +1 -1
  135. package/dist/modules/grid.d.ts +1 -1
  136. package/dist/modules/i18n.d.ts +17 -0
  137. package/dist/modules/i18n.js +22 -0
  138. package/dist/modules/index.d.ts +2 -1
  139. package/dist/modules/index.js +24 -1
  140. package/dist/modules/integration.d.ts +1 -1
  141. package/dist/modules/json.d.ts +1 -1
  142. package/dist/modules/llm.d.ts +1 -1
  143. package/dist/modules/math.d.ts +1 -1
  144. package/dist/modules/memory.d.ts +1 -1
  145. package/dist/modules/nn.d.ts +1 -1
  146. package/dist/modules/noise.d.ts +1 -1
  147. package/dist/modules/object.d.ts +1 -1
  148. package/dist/modules/os.d.ts +1 -1
  149. package/dist/modules/path.d.ts +1 -1
  150. package/dist/modules/prob.d.ts +1 -1
  151. package/dist/modules/session.d.ts +1 -1
  152. package/dist/modules/str.d.ts +1 -1
  153. package/dist/modules/tensor.d.ts +1 -1
  154. package/dist/modules/time.d.ts +1 -1
  155. package/dist/modules/trace.d.ts +1 -1
  156. package/dist/modules/train.d.ts +1 -1
  157. package/dist/modules/validate.d.ts +1 -1
  158. package/dist/modules/vector.d.ts +1 -1
  159. package/dist/modules/workspace.d.ts +1 -1
  160. package/dist/registry/factory-signatures.json +1 -1
  161. package/dist/registry.d.ts +10 -3
  162. package/dist/registry.js +31 -1
  163. package/dist/{types-DLx38UFZ.d.ts → types-Cdb321uM.d.ts} +9 -3
  164. package/i18n/ar.json +1 -0
  165. package/i18n/en.json +1 -0
  166. package/i18n/sl.json +1 -0
  167. package/package.json +3 -3
@@ -1,5 +1,5 @@
1
1
  app std-agent-session "3.0.0"
2
- "std-agent-session — the conversation with an in-app assistant. AgentThread owns the persisted AgentMessage rows (role, content, ownerId stamped with the real @user, createdAt): SEND stores the user's message, reloads the thread and emits CONVERSE with the whole conversation, oldest first, for std-agent-tool-loop to answer; REPLIED stores the reply. AgentConversation is what the user sees, in the browser: the thread as chat bubbles with every step the assistant takes interleaved live (STEP: each model call, each tool it used and what the app answered), a thinking row while it works, and a chat bar. Rows are meant to be private to their owner: owner-scoped access needs an [identity] roster, so the binding organism declares the @read/@create scoping on AgentMessage — this generic atom cannot."
2
+ "std-agent-session — the conversation with an in-app assistant. AgentThread owns the persisted AgentMessage rows (role, content, ownerId stamped with the real @user, createdAt): SEND stores the user's message, reloads the thread and emits CONVERSE with the whole conversation, oldest first, for std-agent-tool-loop to answer; REPLIED stores the reply. AgentConversation is what the user sees, in the browser: the thread as chat bubbles with every step the assistant takes interleaved live (STEP: each model call, each tool it used and what the app answered), a thinking row while it works, and a chat bar. The two talk over the assistant contract, from any orbital: AgentConversation emits ASSISTANT_SEND and hears AgentMessagesLoaded, ASSISTANT_THREAD_FAILED, ASSISTANT_STEP, ASSISTANT_REPLIED and ASSISTANT_REPLY_FAILED; AgentThread hears ASSISTANT_SEND. So a conversation placed on any section's page reaches the app's one assistant (std-agent-assistant) with no wiring. Rows are meant to be private to their owner: owner-scoped access needs an [identity] roster, so the binding organism declares the @read/@create scoping on AgentMessage — this generic atom cannot."
3
3
 
4
4
  type AgentChatMessage = {
5
5
  role : "user" | "assistant"!
@@ -9,6 +9,16 @@ type AgentLink = {
9
9
  label : string!
10
10
  href : string!
11
11
  }
12
+ type AgentChatRow = {
13
+ id : string!
14
+ title : string
15
+ lastActiveAt : number
16
+ }
17
+ type AgentMemoryRow = {
18
+ id : string!
19
+ content : string!
20
+ createdAt : number
21
+ }
12
22
  type AgentThreadRow = {
13
23
  role : "user" | "assistant"!
14
24
  content : string!
@@ -79,6 +89,7 @@ orbital AgentSessionOrbital {
79
89
  role : "user" | "assistant" @description "Who said it." @synonyms "speaker, author" = user
80
90
  content : string! @description "What was said." @synonyms "text, message, body"
81
91
  createdAt : number @description "When it was said (ms)." @synonyms "time, timestamp" = 0
92
+ chatId : string @description "The conversation this message belongs to (an AgentChat id)." @synonyms "conversation, thread, session" = ""
82
93
  }
83
94
 
84
95
  entity AgentConversationView [runtime] {
@@ -92,12 +103,17 @@ orbital AgentSessionOrbital {
92
103
 
93
104
  state ready {
94
105
  INIT -> ready
106
+ OPEN_CHAT -> ready when ["=", (str/default ?chatId ""), ""]
107
+ (emit AgentMessagesLoaded { data: [] })
108
+ OPEN_CHAT -> ready when (not ["=", (str/default ?chatId ""), ""])
95
109
  (fetch AgentMessage {
96
110
  emit: { failure: AgentMessagesLoadFailed, success: AgentMessagesLoaded }
111
+ filter: ["=", (object/get @entity chatId), ?chatId]
97
112
  })
98
- SEND -> ready when (not ["=", (str/default ?message ""), ""])
113
+ SEND_IN_CHAT -> ready when (not ["=", (str/default ?message ""), ""])
99
114
  (persist create AgentMessage {
100
115
  ownerId: @user.id
116
+ chatId: ?chatId
101
117
  role: user
102
118
  content: ?message
103
119
  createdAt: @now
@@ -107,27 +123,29 @@ orbital AgentSessionOrbital {
107
123
  AgentMessageSaved -> ready
108
124
  (fetch AgentMessage {
109
125
  emit: { failure: AgentMessagesLoadFailed, success: AgentConversationLoaded }
126
+ filter: ["=", (object/get @entity chatId), ?chatId]
110
127
  })
111
128
  AgentConversationLoaded -> ready
112
129
  (emit CONVERSE {
113
130
  messages: (array/map (array/sort ?data createdAt asc) (fn item { role: @item.role, content: @item.content }))
114
131
  })
115
- REPLIED -> ready
132
+ REPLY_IN_CHAT -> ready
116
133
  (persist create AgentMessage {
117
134
  ownerId: @user.id
135
+ chatId: ?chatId
118
136
  role: assistant
119
137
  content: ?reply
120
138
  createdAt: @now
121
139
  })
122
140
  AgentMessagesLoadFailed -> ready
123
- (emit THREAD_FAILED { error: ?error })
141
+ (emit ASSISTANT_THREAD_FAILED { error: ?error })
124
142
  (render-ui toast {
125
143
  message: ?error
126
144
  type: alert
127
145
  variant: error
128
146
  })
129
147
  AgentMessageSaveFailed -> ready
130
- (emit THREAD_FAILED { error: ?error })
148
+ (emit ASSISTANT_THREAD_FAILED { error: ?error })
131
149
  (render-ui toast {
132
150
  message: ?error
133
151
  type: alert
@@ -136,7 +154,7 @@ orbital AgentSessionOrbital {
136
154
  }
137
155
 
138
156
  emits {
139
- THREAD_FAILED -> external {
157
+ ASSISTANT_THREAD_FAILED -> external {
140
158
  error : string!
141
159
  }
142
160
  @description "The conversation could not be loaded or a message could not be stored."
@@ -145,13 +163,13 @@ orbital AgentSessionOrbital {
145
163
  CONVERSE -> external {
146
164
  messages : [AgentChatMessage]!
147
165
  }
148
- @description "The conversation to answer, oldest first; the last turn is the request."
166
+ @description "The current conversation to answer, oldest first; the last turn is the request."
149
167
  @synonyms "ask, converse, request"
150
168
  @tier "domain"
151
169
  AgentMessagesLoaded -> external {
152
170
  data : [AgentMessage]
153
171
  }
154
- @description "The conversation finished loading."
172
+ @description "The current conversation's messages finished loading (empty for a new conversation)."
155
173
  @tier "internal"
156
174
  AgentMessagesLoadFailed -> external {
157
175
  error : string
@@ -175,16 +193,24 @@ orbital AgentSessionOrbital {
175
193
  }
176
194
 
177
195
  listens {
178
- AgentConversation.SEND -> SEND
179
- SEND {
196
+ *.ASSISTANT_CHAT_OPENED -> OPEN_CHAT
197
+ *.ASSISTANT_SEND_IN_CHAT -> SEND_IN_CHAT
198
+ *.ASSISTANT_REPLY_IN_CHAT -> REPLY_IN_CHAT
199
+ OPEN_CHAT {
200
+ chatId : string
201
+ }
202
+ @description "Load a conversation's messages (none for a new one)."
203
+ SEND_IN_CHAT {
204
+ chatId : string!
180
205
  message : string!
181
206
  }
182
- @description "Store the user's message and ask the assistant."
207
+ @description "Store the user's message in this conversation and ask the assistant."
183
208
  @synonyms "ask, message, chat"
184
- REPLIED {
209
+ REPLY_IN_CHAT {
210
+ chatId : string
185
211
  reply : string!
186
212
  }
187
- @description "Store the assistant's answer."
213
+ @description "Store the assistant's answer in this conversation."
188
214
  }
189
215
  }
190
216
 
@@ -196,25 +222,89 @@ orbital AgentSessionOrbital {
196
222
  emptyTitle : string = "Ask me to do something in this app"
197
223
  @label "Empty conversation title"
198
224
  @tier "presentation"
199
- links : [AgentLink] = []
225
+ links : [AgentLink] = [{ label: "All conversations", href: "/agent-session/chats" }]
200
226
  @label "Links above the conversation"
201
227
  @description "Related pages (e.g. the run history)."
202
228
  @tier "presentation"
229
+ newChatLabel : string = "New conversation"
230
+ @label "New conversation button"
231
+ @description "Starts a fresh conversation; earlier ones stay in the conversation list."
232
+ @tier "presentation"
233
+ chatsLabel : string = "Conversations"
234
+ @label "Past conversations button"
235
+ @description "Shows the person's past conversations in place of the chat; picking one reopens it."
236
+ @tier "presentation"
237
+ memoryLabel : string = "What I remember"
238
+ @label "Memory button"
239
+ @description "Shows what the assistant remembers for this person in place of the chat."
240
+ @tier "presentation"
241
+ untitledLabel : string = "New conversation"
242
+ @label "Title of a conversation with no messages yet"
243
+ @tier "presentation"
244
+ chatsEmptyTitle : string = "No conversations yet"
245
+ @label "Empty conversation list"
246
+ @tier "presentation"
247
+ memoryEmptyTitle : string = "Nothing remembered yet"
248
+ @label "Empty memory list"
249
+ @tier "presentation"
250
+ backLabel : string = "Back"
251
+ @label "Back to the conversation"
252
+ @tier "presentation"
203
253
  }
204
254
  initial: idle
205
255
 
206
256
  state idle {
207
257
  INIT -> idle
258
+ (emit ASSISTANT_HISTORY_REQUESTED {})
208
259
  (render-ui main {
209
260
  children: [{
210
- children: (array/map @config.links (fn item {
211
- href: @item.href
212
- label: @item.label
261
+ children: [{
262
+ action: NEW_CHAT_CLICKED
263
+ aria-label: @config.newChatLabel
264
+ icon: message-square-plus
265
+ label: @config.newChatLabel
266
+ size: sm
213
267
  type: button
214
- variant: ghost
215
- }))
268
+ variant: secondary
269
+ }, {
270
+ children: [{
271
+ action: OPEN_CHATS_CLICKED
272
+ aria-label: @config.chatsLabel
273
+ label: @config.chatsLabel
274
+ size: sm
275
+ type: button
276
+ variant: ghost
277
+ }, {
278
+ action: OPEN_MEMORY_CLICKED
279
+ aria-label: @config.memoryLabel
280
+ label: @config.memoryLabel
281
+ size: sm
282
+ type: button
283
+ variant: ghost
284
+ }, {
285
+ children: (array/map @config.links (fn item {
286
+ href: @item.href
287
+ label: @item.label
288
+ size: sm
289
+ type: button
290
+ variant: ghost
291
+ }))
292
+ direction: horizontal
293
+ gap: xs
294
+ type: stack
295
+ }]
296
+ align: center
297
+ className: shrink-0
298
+ direction: horizontal
299
+ gap: none
300
+ type: stack
301
+ }]
302
+ align: center
303
+ className: shrink-0
216
304
  direction: horizontal
217
305
  gap: sm
306
+ justify: between
307
+ wrap: true
218
308
  type: stack
219
309
  }, {
220
310
  activities: @entity.entries
@@ -238,14 +328,53 @@ orbital AgentSessionOrbital {
238
328
  (set @entity.entries (array/map (array/sort ?data createdAt asc) (fn item { type: message, role: @item.role, content: @item.content, timestamp: @item.createdAt })))
239
329
  (render-ui main {
240
330
  children: [{
241
- children: (array/map @config.links (fn item {
242
- href: @item.href
243
- label: @item.label
331
+ children: [{
332
+ action: NEW_CHAT_CLICKED
333
+ aria-label: @config.newChatLabel
334
+ icon: message-square-plus
335
+ label: @config.newChatLabel
336
+ size: sm
244
337
  type: button
245
- variant: ghost
246
- }))
338
+ variant: secondary
339
+ }, {
340
+ children: [{
341
+ action: OPEN_CHATS_CLICKED
342
+ aria-label: @config.chatsLabel
343
+ label: @config.chatsLabel
344
+ size: sm
345
+ type: button
346
+ variant: ghost
347
+ }, {
348
+ action: OPEN_MEMORY_CLICKED
349
+ aria-label: @config.memoryLabel
350
+ label: @config.memoryLabel
351
+ size: sm
352
+ type: button
353
+ variant: ghost
354
+ }, {
355
+ children: (array/map @config.links (fn item {
356
+ href: @item.href
357
+ label: @item.label
358
+ size: sm
359
+ type: button
360
+ variant: ghost
361
+ }))
362
+ direction: horizontal
363
+ gap: xs
364
+ type: stack
365
+ }]
366
+ align: center
367
+ className: shrink-0
368
+ direction: horizontal
369
+ gap: none
370
+ type: stack
371
+ }]
372
+ align: center
373
+ className: shrink-0
247
374
  direction: horizontal
248
375
  gap: sm
376
+ justify: between
377
+ wrap: true
249
378
  type: stack
250
379
  }, {
251
380
  activities: @entity.entries
@@ -268,17 +397,56 @@ orbital AgentSessionOrbital {
268
397
  ASK -> idle when (and (not @entity.working) (not ["=", (str/default ?message ""), ""]))
269
398
  (set @entity.entries (array/append @entity.entries { type: message, role: user, content: ?message, timestamp: @now }))
270
399
  (set @entity.working true)
271
- (emit SEND { message: ?message })
400
+ (emit ASSISTANT_SEND { message: ?message })
272
401
  (render-ui main {
273
402
  children: [{
274
- children: (array/map @config.links (fn item {
275
- href: @item.href
276
- label: @item.label
403
+ children: [{
404
+ action: NEW_CHAT_CLICKED
405
+ aria-label: @config.newChatLabel
406
+ icon: message-square-plus
407
+ label: @config.newChatLabel
408
+ size: sm
277
409
  type: button
278
- variant: ghost
279
- }))
410
+ variant: secondary
411
+ }, {
412
+ children: [{
413
+ action: OPEN_CHATS_CLICKED
414
+ aria-label: @config.chatsLabel
415
+ label: @config.chatsLabel
416
+ size: sm
417
+ type: button
418
+ variant: ghost
419
+ }, {
420
+ action: OPEN_MEMORY_CLICKED
421
+ aria-label: @config.memoryLabel
422
+ label: @config.memoryLabel
423
+ size: sm
424
+ type: button
425
+ variant: ghost
426
+ }, {
427
+ children: (array/map @config.links (fn item {
428
+ href: @item.href
429
+ label: @item.label
430
+ size: sm
431
+ type: button
432
+ variant: ghost
433
+ }))
434
+ direction: horizontal
435
+ gap: xs
436
+ type: stack
437
+ }]
438
+ align: center
439
+ className: shrink-0
440
+ direction: horizontal
441
+ gap: none
442
+ type: stack
443
+ }]
444
+ align: center
445
+ className: shrink-0
280
446
  direction: horizontal
281
447
  gap: sm
448
+ justify: between
449
+ wrap: true
282
450
  type: stack
283
451
  }, {
284
452
  activities: @entity.entries
@@ -302,14 +470,53 @@ orbital AgentSessionOrbital {
302
470
  (set @entity.entries (array/append @entity.entries ?activity))
303
471
  (render-ui main {
304
472
  children: [{
305
- children: (array/map @config.links (fn item {
306
- href: @item.href
307
- label: @item.label
473
+ children: [{
474
+ action: NEW_CHAT_CLICKED
475
+ aria-label: @config.newChatLabel
476
+ icon: message-square-plus
477
+ label: @config.newChatLabel
478
+ size: sm
308
479
  type: button
309
- variant: ghost
310
- }))
480
+ variant: secondary
481
+ }, {
482
+ children: [{
483
+ action: OPEN_CHATS_CLICKED
484
+ aria-label: @config.chatsLabel
485
+ label: @config.chatsLabel
486
+ size: sm
487
+ type: button
488
+ variant: ghost
489
+ }, {
490
+ action: OPEN_MEMORY_CLICKED
491
+ aria-label: @config.memoryLabel
492
+ label: @config.memoryLabel
493
+ size: sm
494
+ type: button
495
+ variant: ghost
496
+ }, {
497
+ children: (array/map @config.links (fn item {
498
+ href: @item.href
499
+ label: @item.label
500
+ size: sm
501
+ type: button
502
+ variant: ghost
503
+ }))
504
+ direction: horizontal
505
+ gap: xs
506
+ type: stack
507
+ }]
508
+ align: center
509
+ className: shrink-0
510
+ direction: horizontal
511
+ gap: none
512
+ type: stack
513
+ }]
514
+ align: center
515
+ className: shrink-0
311
516
  direction: horizontal
312
517
  gap: sm
518
+ justify: between
519
+ wrap: true
313
520
  type: stack
314
521
  }, {
315
522
  activities: @entity.entries
@@ -334,15 +541,369 @@ orbital AgentSessionOrbital {
334
541
  (set @entity.working false)
335
542
  (render-ui main {
336
543
  children: [{
337
- children: (array/map @config.links (fn item {
338
- href: @item.href
339
- label: @item.label
544
+ children: [{
545
+ action: NEW_CHAT_CLICKED
546
+ aria-label: @config.newChatLabel
547
+ icon: message-square-plus
548
+ label: @config.newChatLabel
549
+ size: sm
550
+ type: button
551
+ variant: secondary
552
+ }, {
553
+ children: [{
554
+ action: OPEN_CHATS_CLICKED
555
+ aria-label: @config.chatsLabel
556
+ label: @config.chatsLabel
557
+ size: sm
558
+ type: button
559
+ variant: ghost
560
+ }, {
561
+ action: OPEN_MEMORY_CLICKED
562
+ aria-label: @config.memoryLabel
563
+ label: @config.memoryLabel
564
+ size: sm
565
+ type: button
566
+ variant: ghost
567
+ }, {
568
+ children: (array/map @config.links (fn item {
569
+ href: @item.href
570
+ label: @item.label
571
+ size: sm
572
+ type: button
573
+ variant: ghost
574
+ }))
575
+ direction: horizontal
576
+ gap: xs
577
+ type: stack
578
+ }]
579
+ align: center
580
+ className: shrink-0
581
+ direction: horizontal
582
+ gap: none
583
+ type: stack
584
+ }]
585
+ align: center
586
+ className: shrink-0
587
+ direction: horizontal
588
+ gap: sm
589
+ justify: between
590
+ wrap: true
591
+ type: stack
592
+ }, {
593
+ activities: @entity.entries
594
+ className: flex-1
595
+ emptyTitle: @config.emptyTitle
596
+ idPrefix: agent-conversation
597
+ rawLlmPreferenceKey: agent-conversation-raw-llm
598
+ type: agent-chat-feed
599
+ working: @entity.working
600
+ }, {
601
+ placeholder: @config.placeholder
602
+ sendEvent: ASK
603
+ type: chat-bar
604
+ }]
605
+ className: "h-full min-h-0 flex-1"
606
+ direction: vertical
607
+ gap: sm
608
+ type: stack
609
+ })
610
+ NEW_CHAT_CLICKED -> idle when (not @entity.working)
611
+ (set @entity.entries [])
612
+ (emit ASSISTANT_NEW_CHAT {})
613
+ (render-ui main {
614
+ children: [{
615
+ children: [{
616
+ action: NEW_CHAT_CLICKED
617
+ aria-label: @config.newChatLabel
618
+ icon: message-square-plus
619
+ label: @config.newChatLabel
620
+ size: sm
621
+ type: button
622
+ variant: secondary
623
+ }, {
624
+ children: [{
625
+ action: OPEN_CHATS_CLICKED
626
+ aria-label: @config.chatsLabel
627
+ label: @config.chatsLabel
628
+ size: sm
629
+ type: button
630
+ variant: ghost
631
+ }, {
632
+ action: OPEN_MEMORY_CLICKED
633
+ aria-label: @config.memoryLabel
634
+ label: @config.memoryLabel
635
+ size: sm
636
+ type: button
637
+ variant: ghost
638
+ }, {
639
+ children: (array/map @config.links (fn item {
640
+ href: @item.href
641
+ label: @item.label
642
+ size: sm
643
+ type: button
644
+ variant: ghost
645
+ }))
646
+ direction: horizontal
647
+ gap: xs
648
+ type: stack
649
+ }]
650
+ align: center
651
+ className: shrink-0
652
+ direction: horizontal
653
+ gap: none
654
+ type: stack
655
+ }]
656
+ align: center
657
+ className: shrink-0
658
+ direction: horizontal
659
+ gap: sm
660
+ justify: between
661
+ wrap: true
662
+ type: stack
663
+ }, {
664
+ activities: @entity.entries
665
+ className: flex-1
666
+ emptyTitle: @config.emptyTitle
667
+ idPrefix: agent-conversation
668
+ rawLlmPreferenceKey: agent-conversation-raw-llm
669
+ type: agent-chat-feed
670
+ working: @entity.working
671
+ }, {
672
+ placeholder: @config.placeholder
673
+ sendEvent: ASK
674
+ type: chat-bar
675
+ }]
676
+ className: "h-full min-h-0 flex-1"
677
+ direction: vertical
678
+ gap: sm
679
+ type: stack
680
+ })
681
+ OPEN_CHATS_CLICKED -> idle
682
+ (emit ASSISTANT_CHATS_REQUESTED {})
683
+ CHATS_LISTED -> idle
684
+ (render-ui main {
685
+ children: [{
686
+ children: [{
687
+ action: BACK_TO_CHAT
688
+ aria-label: @config.backLabel
689
+ icon: arrow-left
690
+ label: @config.backLabel
691
+ size: sm
340
692
  type: button
341
693
  variant: ghost
342
- }))
694
+ }, {
695
+ content: @config.chatsLabel
696
+ type: typography
697
+ variant: h4
698
+ }]
699
+ align: center
700
+ className: shrink-0
343
701
  direction: horizontal
344
702
  gap: sm
345
703
  type: stack
704
+ }, {
705
+ emptyMessage: @config.chatsEmptyTitle
706
+ entity: ?data
707
+ fields: []
708
+ gap: none
709
+ itemClickEvent: CHAT_PICKED
710
+ renderItem: (fn item {
711
+ children: [{
712
+ className: truncate
713
+ content: (if ["=", (str/default @item.title ""), ""] @config.untitledLabel @item.title)
714
+ type: typography
715
+ variant: body
716
+ }, {
717
+ color: muted
718
+ content: @item.lastActiveAt
719
+ format: date
720
+ type: typography
721
+ variant: caption
722
+ }]
723
+ direction: vertical
724
+ gap: none
725
+ type: stack
726
+ })
727
+ type: data-list
728
+ variant: compact
729
+ }]
730
+ className: "h-full min-h-0 flex-1 overflow-y-auto"
731
+ direction: vertical
732
+ gap: sm
733
+ type: stack
734
+ })
735
+ CHAT_PICKED -> idle when (not @entity.working)
736
+ (emit ASSISTANT_CHAT_SELECTED { chatId: ?id })
737
+ (render-ui main {
738
+ children: [{
739
+ children: [{
740
+ action: NEW_CHAT_CLICKED
741
+ aria-label: @config.newChatLabel
742
+ icon: message-square-plus
743
+ label: @config.newChatLabel
744
+ size: sm
745
+ type: button
746
+ variant: secondary
747
+ }, {
748
+ children: [{
749
+ action: OPEN_CHATS_CLICKED
750
+ aria-label: @config.chatsLabel
751
+ label: @config.chatsLabel
752
+ size: sm
753
+ type: button
754
+ variant: ghost
755
+ }, {
756
+ action: OPEN_MEMORY_CLICKED
757
+ aria-label: @config.memoryLabel
758
+ label: @config.memoryLabel
759
+ size: sm
760
+ type: button
761
+ variant: ghost
762
+ }, {
763
+ children: (array/map @config.links (fn item {
764
+ href: @item.href
765
+ label: @item.label
766
+ size: sm
767
+ type: button
768
+ variant: ghost
769
+ }))
770
+ direction: horizontal
771
+ gap: xs
772
+ type: stack
773
+ }]
774
+ align: center
775
+ className: shrink-0
776
+ direction: horizontal
777
+ gap: none
778
+ type: stack
779
+ }]
780
+ align: center
781
+ className: shrink-0
782
+ direction: horizontal
783
+ gap: sm
784
+ justify: between
785
+ wrap: true
786
+ type: stack
787
+ }, {
788
+ activities: @entity.entries
789
+ className: flex-1
790
+ emptyTitle: @config.emptyTitle
791
+ idPrefix: agent-conversation
792
+ rawLlmPreferenceKey: agent-conversation-raw-llm
793
+ type: agent-chat-feed
794
+ working: @entity.working
795
+ }, {
796
+ placeholder: @config.placeholder
797
+ sendEvent: ASK
798
+ type: chat-bar
799
+ }]
800
+ className: "h-full min-h-0 flex-1"
801
+ direction: vertical
802
+ gap: sm
803
+ type: stack
804
+ })
805
+ OPEN_MEMORY_CLICKED -> idle
806
+ (emit ASSISTANT_MEMORIES_REQUESTED {})
807
+ MEMORIES_LISTED -> idle
808
+ (render-ui main {
809
+ children: [{
810
+ children: [{
811
+ action: BACK_TO_CHAT
812
+ aria-label: @config.backLabel
813
+ icon: arrow-left
814
+ label: @config.backLabel
815
+ size: sm
816
+ type: button
817
+ variant: ghost
818
+ }, {
819
+ content: @config.memoryLabel
820
+ type: typography
821
+ variant: h4
822
+ }]
823
+ align: center
824
+ className: shrink-0
825
+ direction: horizontal
826
+ gap: sm
827
+ type: stack
828
+ }, {
829
+ emptyMessage: @config.memoryEmptyTitle
830
+ entity: ?data
831
+ fields: []
832
+ gap: sm
833
+ renderItem: (fn item {
834
+ children: [{
835
+ content: @item.content
836
+ type: typography
837
+ variant: body
838
+ }, {
839
+ color: muted
840
+ content: @item.createdAt
841
+ format: date
842
+ type: typography
843
+ variant: caption
844
+ }]
845
+ direction: vertical
846
+ gap: none
847
+ type: stack
848
+ })
849
+ type: data-list
850
+ }]
851
+ className: "h-full min-h-0 flex-1 overflow-y-auto"
852
+ direction: vertical
853
+ gap: sm
854
+ type: stack
855
+ })
856
+ BACK_TO_CHAT -> idle
857
+ (render-ui main {
858
+ children: [{
859
+ children: [{
860
+ action: NEW_CHAT_CLICKED
861
+ aria-label: @config.newChatLabel
862
+ icon: message-square-plus
863
+ label: @config.newChatLabel
864
+ size: sm
865
+ type: button
866
+ variant: secondary
867
+ }, {
868
+ children: [{
869
+ action: OPEN_CHATS_CLICKED
870
+ aria-label: @config.chatsLabel
871
+ label: @config.chatsLabel
872
+ size: sm
873
+ type: button
874
+ variant: ghost
875
+ }, {
876
+ action: OPEN_MEMORY_CLICKED
877
+ aria-label: @config.memoryLabel
878
+ label: @config.memoryLabel
879
+ size: sm
880
+ type: button
881
+ variant: ghost
882
+ }, {
883
+ children: (array/map @config.links (fn item {
884
+ href: @item.href
885
+ label: @item.label
886
+ size: sm
887
+ type: button
888
+ variant: ghost
889
+ }))
890
+ direction: horizontal
891
+ gap: xs
892
+ type: stack
893
+ }]
894
+ align: center
895
+ className: shrink-0
896
+ direction: horizontal
897
+ gap: none
898
+ type: stack
899
+ }]
900
+ align: center
901
+ className: shrink-0
902
+ direction: horizontal
903
+ gap: sm
904
+ justify: between
905
+ wrap: true
906
+ type: stack
346
907
  }, {
347
908
  activities: @entity.entries
348
909
  className: flex-1
@@ -366,14 +927,53 @@ orbital AgentSessionOrbital {
366
927
  (set @entity.working false)
367
928
  (render-ui main {
368
929
  children: [{
369
- children: (array/map @config.links (fn item {
370
- href: @item.href
371
- label: @item.label
930
+ children: [{
931
+ action: NEW_CHAT_CLICKED
932
+ aria-label: @config.newChatLabel
933
+ icon: message-square-plus
934
+ label: @config.newChatLabel
935
+ size: sm
372
936
  type: button
373
- variant: ghost
374
- }))
937
+ variant: secondary
938
+ }, {
939
+ children: [{
940
+ action: OPEN_CHATS_CLICKED
941
+ aria-label: @config.chatsLabel
942
+ label: @config.chatsLabel
943
+ size: sm
944
+ type: button
945
+ variant: ghost
946
+ }, {
947
+ action: OPEN_MEMORY_CLICKED
948
+ aria-label: @config.memoryLabel
949
+ label: @config.memoryLabel
950
+ size: sm
951
+ type: button
952
+ variant: ghost
953
+ }, {
954
+ children: (array/map @config.links (fn item {
955
+ href: @item.href
956
+ label: @item.label
957
+ size: sm
958
+ type: button
959
+ variant: ghost
960
+ }))
961
+ direction: horizontal
962
+ gap: xs
963
+ type: stack
964
+ }]
965
+ align: center
966
+ className: shrink-0
967
+ direction: horizontal
968
+ gap: none
969
+ type: stack
970
+ }]
971
+ align: center
972
+ className: shrink-0
375
973
  direction: horizontal
376
974
  gap: sm
975
+ justify: between
976
+ wrap: true
377
977
  type: stack
378
978
  }, {
379
979
  activities: @entity.entries
@@ -396,12 +996,57 @@ orbital AgentSessionOrbital {
396
996
  }
397
997
 
398
998
  emits {
399
- SEND -> external {
999
+ ASSISTANT_HISTORY_REQUESTED -> external {
1000
+ }
1001
+ @description "This conversation view opened; the assistant thread loads the current conversation into it."
1002
+ @synonyms "load history, restore conversation"
1003
+ @tier "domain"
1004
+ ASSISTANT_NEW_CHAT -> external {
1005
+ }
1006
+ @description "The user started a new conversation."
1007
+ @synonyms "new chat, new conversation, start over"
1008
+ @tier "domain"
1009
+ NEW_CHAT_CLICKED {
1010
+ }
1011
+ @description "Start a new conversation."
1012
+ @synonyms "new chat, start over, clear"
1013
+ @tier "presentation"
1014
+ ASSISTANT_SEND -> external {
400
1015
  message : string!
401
1016
  }
402
1017
  @description "The user asked something; AgentThread stores it and asks the assistant."
403
1018
  @synonyms "ask, send, submit"
404
1019
  @tier "domain"
1020
+ ASSISTANT_CHATS_REQUESTED -> external {
1021
+ }
1022
+ @description "The user opened the conversation list; the conversation keeper sends the person's conversations."
1023
+ @synonyms "conversations, history, past chats"
1024
+ @tier "domain"
1025
+ ASSISTANT_CHAT_SELECTED -> external {
1026
+ chatId : string!
1027
+ }
1028
+ @description "The user picked a past conversation to reopen."
1029
+ @synonyms "open conversation, reopen chat"
1030
+ @tier "domain"
1031
+ ASSISTANT_MEMORIES_REQUESTED -> external {
1032
+ }
1033
+ @description "The user opened what the assistant remembers; the assistant's memory sends it."
1034
+ @synonyms "memories, what I remember"
1035
+ @tier "domain"
1036
+ OPEN_CHATS_CLICKED {
1037
+ }
1038
+ @description "Show past conversations."
1039
+ @tier "presentation"
1040
+ OPEN_MEMORY_CLICKED {
1041
+ }
1042
+ @description "Show what the assistant remembers."
1043
+ @tier "presentation"
1044
+ CHAT_PICKED {
1045
+ id : string!
1046
+ row : AgentChatRow
1047
+ }
1048
+ @description "Reopen the picked conversation."
1049
+ @tier "presentation"
405
1050
  ASK {
406
1051
  message : string!
407
1052
  }
@@ -411,8 +1056,24 @@ orbital AgentSessionOrbital {
411
1056
  }
412
1057
 
413
1058
  listens {
414
- AgentThread.AgentMessagesLoaded -> THREAD_LOADED
415
- AgentThread.THREAD_FAILED -> REPLY_FAILED
1059
+ *.AgentMessagesLoaded -> THREAD_LOADED
1060
+ *.ASSISTANT_THREAD_FAILED -> REPLY_FAILED
1061
+ *.ASSISTANT_STEP -> STEP
1062
+ *.ASSISTANT_REPLIED -> REPLIED
1063
+ *.ASSISTANT_REPLY_FAILED -> REPLY_FAILED
1064
+ *.ASSISTANT_CHATS_LOADED -> CHATS_LISTED
1065
+ *.ASSISTANT_MEMORIES_LOADED -> MEMORIES_LISTED
1066
+ CHATS_LISTED {
1067
+ data : [AgentChatRow]
1068
+ }
1069
+ @description "Show the person's conversations to pick from."
1070
+ MEMORIES_LISTED {
1071
+ data : [AgentMemoryRow]
1072
+ }
1073
+ @description "Show what the assistant remembers."
1074
+ BACK_TO_CHAT {
1075
+ }
1076
+ @description "Back to the conversation."
416
1077
  THREAD_LOADED {
417
1078
  data : [AgentThreadRow]
418
1079
  }
@@ -435,3 +1096,369 @@ orbital AgentSessionOrbital {
435
1096
 
436
1097
  page "/agent-session" as AgentSessionPage -> AgentConversation, AgentThread
437
1098
  }
1099
+
1100
+ ;; Conversations: each person's kept chats, the current one (most recently
1101
+ ;; used) and the list of past ones.
1102
+ orbital AgentChatsOrbital {
1103
+ type ChatReadyForSend = Event AgentChat "Fired when the current conversation exists and is marked active"
1104
+ type NewChatStarted = Event AgentChat "Fired after a new conversation persists"
1105
+ type ChatSelected = Event AgentChat "Fired after a past conversation is reopened"
1106
+
1107
+ entity AgentChat [persistent: agentChats] {
1108
+ id : string!
1109
+ ownerId : string! @description "Whose conversation this is (the real @user). The binding organism scopes @read to it." @synonyms "user, owner, account"
1110
+ title : string @description "The conversation's first request, shown in the conversation list; empty until the first message." @synonyms "name, subject, topic" = ""
1111
+ startedAt : number @description "When the conversation started (ms)." @synonyms "created, opened" = 0
1112
+ lastActiveAt : number @description "When the conversation was last used (ms). The most recent one is the current conversation." @synonyms "last used, updated, recent" = 0
1113
+ pendingMessage : string @intrinsic = ""
1114
+ pendingReply : string @intrinsic = ""
1115
+ }
1116
+
1117
+ ;; Which conversation is current: each person's most recently used AgentChat.
1118
+ ;; It starts, reopens and touches conversations and tells AgentThread which
1119
+ ;; one a request belongs to, so only this trait (bound to AgentChat) touches
1120
+ ;; conversation rows.
1121
+ trait AgentChatKeeper -> @rebindable AgentChat [lifecycle] {
1122
+ initial: ready
1123
+
1124
+ state ready {
1125
+ INIT -> ready
1126
+ LOAD_HISTORY -> ready
1127
+ (fetch AgentChat {
1128
+ emit: { failure: AgentChatsLoadFailed, success: ChatsForHistory }
1129
+ })
1130
+ ChatsForHistory -> ready
1131
+ (emit ASSISTANT_CHAT_OPENED { chatId: (if [">", (array/len ?data), 0] (object/get (array/first (array/sort ?data lastActiveAt desc)) id) "") })
1132
+ SEND -> ready when (not ["=", (str/default ?message ""), ""])
1133
+ (set @entity.pendingMessage ?message)
1134
+ (fetch AgentChat {
1135
+ emit: { failure: AgentChatsLoadFailed, success: ChatsForSend }
1136
+ })
1137
+ ChatsForSend -> ready when ["=", (array/len ?data), 0]
1138
+ (persist create AgentChat {
1139
+ ownerId: @user.id
1140
+ title: @entity.pendingMessage
1141
+ startedAt: @now
1142
+ lastActiveAt: @now
1143
+ } {
1144
+ emit: { failure: AgentChatsLoadFailed, success: ChatReadyForSend }
1145
+ })
1146
+ ChatsForSend -> ready when [">", (array/len ?data), 0]
1147
+ (persist update AgentChat {
1148
+ id: (object/get (array/first (array/sort ?data lastActiveAt desc)) id)
1149
+ ownerId: (object/get (array/first (array/sort ?data lastActiveAt desc)) ownerId)
1150
+ title: (if ["=", (str/default (object/get (array/first (array/sort ?data lastActiveAt desc)) title) ""), ""] @entity.pendingMessage (object/get (array/first (array/sort ?data lastActiveAt desc)) title))
1151
+ startedAt: (object/get (array/first (array/sort ?data lastActiveAt desc)) startedAt)
1152
+ lastActiveAt: @now
1153
+ } {
1154
+ emit: { failure: AgentChatsLoadFailed, success: ChatReadyForSend }
1155
+ })
1156
+ ChatReadyForSend -> ready
1157
+ (emit ASSISTANT_SEND_IN_CHAT { chatId: ?id, message: @entity.pendingMessage })
1158
+ (emit ASSISTANT_CHATS_CHANGED {})
1159
+ REPLIED -> ready
1160
+ (set @entity.pendingReply ?reply)
1161
+ (fetch AgentChat {
1162
+ emit: { failure: AgentChatsLoadFailed, success: ChatsForReply }
1163
+ })
1164
+ ChatsForReply -> ready
1165
+ (emit ASSISTANT_REPLY_IN_CHAT { chatId: (if [">", (array/len ?data), 0] (object/get (array/first (array/sort ?data lastActiveAt desc)) id) ""), reply: @entity.pendingReply })
1166
+ NEW_CHAT -> ready
1167
+ (persist create AgentChat {
1168
+ ownerId: @user.id
1169
+ title: ""
1170
+ startedAt: @now
1171
+ lastActiveAt: @now
1172
+ } {
1173
+ emit: { failure: AgentChatsLoadFailed, success: NewChatStarted }
1174
+ })
1175
+ NewChatStarted -> ready
1176
+ (emit ASSISTANT_CHAT_OPENED { chatId: ?id })
1177
+ (emit ASSISTANT_CHATS_CHANGED {})
1178
+ SELECT_CHAT -> ready
1179
+ (fetch AgentChat {
1180
+ emit: { failure: AgentChatsLoadFailed, success: ChatsForSelect }
1181
+ filter: ["=", (object/get @entity id), ?chatId]
1182
+ })
1183
+ ChatsForSelect -> ready when [">", (array/len ?data), 0]
1184
+ (persist update AgentChat {
1185
+ id: (object/get (array/first ?data) id)
1186
+ ownerId: (object/get (array/first ?data) ownerId)
1187
+ title: (object/get (array/first ?data) title)
1188
+ startedAt: (object/get (array/first ?data) startedAt)
1189
+ lastActiveAt: @now
1190
+ } {
1191
+ emit: { failure: AgentChatsLoadFailed, success: ChatSelected }
1192
+ })
1193
+ ChatSelected -> ready
1194
+ (emit ASSISTANT_CHAT_OPENED { chatId: ?id })
1195
+ (emit ASSISTANT_CHATS_CHANGED {})
1196
+ LIST_CHATS -> ready
1197
+ (fetch AgentChat {
1198
+ emit: { failure: AgentChatsLoadFailed, success: ChatsForList }
1199
+ })
1200
+ ChatsForList -> ready
1201
+ (emit ASSISTANT_CHATS_LOADED { data: (array/map (array/sort ?data lastActiveAt desc) (fn item { id: @item.id, title: @item.title, lastActiveAt: @item.lastActiveAt })) })
1202
+ AgentChatsLoadFailed -> ready
1203
+ (emit ASSISTANT_THREAD_FAILED { error: ?error })
1204
+ (render-ui toast {
1205
+ message: ?error
1206
+ type: alert
1207
+ variant: error
1208
+ })
1209
+ }
1210
+
1211
+ emits {
1212
+ ASSISTANT_CHAT_OPENED -> external {
1213
+ chatId : string
1214
+ }
1215
+ @description "This conversation is now the current one (empty for none yet); AgentThread loads its messages."
1216
+ @tier "internal"
1217
+ ASSISTANT_SEND_IN_CHAT -> external {
1218
+ chatId : string!
1219
+ message : string!
1220
+ }
1221
+ @description "The user's message, placed in the current conversation."
1222
+ @tier "internal"
1223
+ ASSISTANT_REPLY_IN_CHAT -> external {
1224
+ chatId : string
1225
+ reply : string!
1226
+ }
1227
+ @description "The assistant's answer, placed in the current conversation."
1228
+ @tier "internal"
1229
+ ASSISTANT_CHATS_CHANGED -> external {
1230
+ }
1231
+ @description "A conversation was started, used or reopened; the conversation list refreshes."
1232
+ @synonyms "conversations changed, history changed"
1233
+ @tier "domain"
1234
+ ASSISTANT_THREAD_FAILED -> external {
1235
+ error : string!
1236
+ }
1237
+ @description "A conversation could not be loaded or stored."
1238
+ @tier "domain"
1239
+ ASSISTANT_CHATS_LOADED -> external {
1240
+ data : [AgentChatRow]!
1241
+ }
1242
+ @description "The person's conversations, most recently used first."
1243
+ @synonyms "conversations, history"
1244
+ @tier "domain"
1245
+ ChatsForList -> internal {
1246
+ data : [AgentChat]
1247
+ }
1248
+ @tier "internal"
1249
+ ChatsForHistory -> internal {
1250
+ data : [AgentChat]
1251
+ }
1252
+ @tier "internal"
1253
+ ChatsForSend -> internal {
1254
+ data : [AgentChat]
1255
+ }
1256
+ @tier "internal"
1257
+ ChatsForReply -> internal {
1258
+ data : [AgentChat]
1259
+ }
1260
+ @tier "internal"
1261
+ ChatsForSelect -> internal {
1262
+ data : [AgentChat]
1263
+ }
1264
+ @tier "internal"
1265
+ ChatReadyForSend
1266
+ @description "The current conversation exists and is marked active — payload is the AgentChat row."
1267
+ @tier "internal"
1268
+ NewChatStarted
1269
+ @description "A new conversation started — payload is the AgentChat row."
1270
+ @tier "internal"
1271
+ ChatSelected
1272
+ @description "A past conversation was reopened — payload is the AgentChat row."
1273
+ @tier "internal"
1274
+ AgentChatsLoadFailed -> external {
1275
+ error : string
1276
+ code : string
1277
+ }
1278
+ @tier "internal"
1279
+ }
1280
+
1281
+ listens {
1282
+ *.ASSISTANT_SEND -> SEND
1283
+ *.ASSISTANT_HISTORY_REQUESTED -> LOAD_HISTORY
1284
+ *.ASSISTANT_NEW_CHAT -> NEW_CHAT
1285
+ *.ASSISTANT_CHAT_SELECTED -> SELECT_CHAT
1286
+ *.ASSISTANT_REPLIED -> REPLIED
1287
+ *.ASSISTANT_CHATS_REQUESTED -> LIST_CHATS
1288
+ LIST_CHATS {
1289
+ }
1290
+ @description "Send the person's conversations to the conversation list."
1291
+ SEND {
1292
+ message : string!
1293
+ }
1294
+ @description "The user asked something; place it in the current conversation."
1295
+ REPLIED {
1296
+ reply : string!
1297
+ }
1298
+ @description "The assistant answered; place the answer in the current conversation."
1299
+ LOAD_HISTORY {
1300
+ }
1301
+ @description "A conversation view opened; open the current conversation."
1302
+ NEW_CHAT {
1303
+ }
1304
+ @description "Start a new conversation; the previous one stays in the list."
1305
+ SELECT_CHAT {
1306
+ chatId : string!
1307
+ }
1308
+ @description "Reopen a past conversation and make it the current one."
1309
+ }
1310
+ }
1311
+
1312
+ trait AgentChatList -> @rebindable AgentChat [interaction, instance] {
1313
+ config {
1314
+ newChatLabel : string = "New conversation"
1315
+ @label "New conversation button"
1316
+ @tier "presentation"
1317
+ untitledLabel : string = "New conversation"
1318
+ @label "Name of a conversation with no messages yet"
1319
+ @tier "presentation"
1320
+ }
1321
+ initial: loading
1322
+
1323
+ state loading {
1324
+ INIT -> loading
1325
+ (fetch AgentChat {
1326
+ emit: { failure: AgentChatsLoadFailed, success: AgentChatsLoaded }
1327
+ })
1328
+ (render-ui main { type: skeleton, variant: list, rows: 4 })
1329
+ AgentChatsLoaded -> browsing
1330
+ (render-ui main {
1331
+ children: [{
1332
+ action: NEW_CHAT_CLICKED
1333
+ aria-label: @config.newChatLabel
1334
+ className: w-full
1335
+ icon: message-square-plus
1336
+ label: @config.newChatLabel
1337
+ type: button
1338
+ variant: secondary
1339
+ }, {
1340
+ emptyDescription: "Your conversations with the assistant appear here."
1341
+ emptyIcon: messages-square
1342
+ emptyTitle: "No conversations yet"
1343
+ entity: (array/sort ?data lastActiveAt desc)
1344
+ itemClickEvent: OPEN_CHAT
1345
+ renderItem: (fn item {
1346
+ children: [{
1347
+ className: truncate
1348
+ content: (if ["=", (str/default @item.title ""), ""] @config.untitledLabel @item.title)
1349
+ type: typography
1350
+ variant: body
1351
+ }]
1352
+ direction: vertical
1353
+ gap: none
1354
+ type: stack
1355
+ })
1356
+ type: data-list
1357
+ variant: compact
1358
+ }]
1359
+ className: "h-full min-h-0 overflow-y-auto"
1360
+ direction: vertical
1361
+ gap: sm
1362
+ type: stack
1363
+ })
1364
+ AgentChatsLoadFailed -> browsing
1365
+ (render-ui main { message: ?error, title: "Couldn't load your conversations", type: alert, variant: error })
1366
+ }
1367
+
1368
+ state browsing {
1369
+ INIT -> loading
1370
+ (fetch AgentChat {
1371
+ emit: { failure: AgentChatsLoadFailed, success: AgentChatsLoaded }
1372
+ })
1373
+ (render-ui main { type: skeleton, variant: list, rows: 4 })
1374
+ CHATS_CHANGED -> browsing
1375
+ (fetch AgentChat {
1376
+ emit: { failure: AgentChatsLoadFailed, success: AgentChatsLoaded }
1377
+ })
1378
+ AgentChatsLoaded -> browsing
1379
+ (render-ui main {
1380
+ children: [{
1381
+ action: NEW_CHAT_CLICKED
1382
+ aria-label: @config.newChatLabel
1383
+ className: w-full
1384
+ icon: message-square-plus
1385
+ label: @config.newChatLabel
1386
+ type: button
1387
+ variant: secondary
1388
+ }, {
1389
+ emptyDescription: "Your conversations with the assistant appear here."
1390
+ emptyIcon: messages-square
1391
+ emptyTitle: "No conversations yet"
1392
+ entity: (array/sort ?data lastActiveAt desc)
1393
+ itemClickEvent: OPEN_CHAT
1394
+ renderItem: (fn item {
1395
+ children: [{
1396
+ className: truncate
1397
+ content: (if ["=", (str/default @item.title ""), ""] @config.untitledLabel @item.title)
1398
+ type: typography
1399
+ variant: body
1400
+ }]
1401
+ direction: vertical
1402
+ gap: none
1403
+ type: stack
1404
+ })
1405
+ type: data-list
1406
+ variant: compact
1407
+ }]
1408
+ className: "h-full min-h-0 overflow-y-auto"
1409
+ direction: vertical
1410
+ gap: sm
1411
+ type: stack
1412
+ })
1413
+ AgentChatsLoadFailed -> browsing
1414
+ (render-ui main { message: ?error, title: "Couldn't load your conversations", type: alert, variant: error })
1415
+ OPEN_CHAT -> browsing
1416
+ (emit ASSISTANT_CHAT_SELECTED { chatId: ?id })
1417
+ NEW_CHAT_CLICKED -> browsing
1418
+ (emit ASSISTANT_NEW_CHAT {})
1419
+ }
1420
+
1421
+ emits {
1422
+ ASSISTANT_CHAT_SELECTED -> external {
1423
+ chatId : string!
1424
+ }
1425
+ @description "The user reopened a past conversation."
1426
+ @synonyms "open conversation, switch conversation"
1427
+ @tier "domain"
1428
+ ASSISTANT_NEW_CHAT -> external {
1429
+ }
1430
+ @description "The user started a new conversation."
1431
+ @synonyms "new chat, new conversation, start over"
1432
+ @tier "domain"
1433
+ AgentChatsLoaded -> external {
1434
+ data : [AgentChat]
1435
+ }
1436
+ @tier "internal"
1437
+ AgentChatsLoadFailed -> external {
1438
+ error : string
1439
+ code : string
1440
+ }
1441
+ @tier "internal"
1442
+ }
1443
+
1444
+ listens {
1445
+ *.ASSISTANT_CHATS_CHANGED -> CHATS_CHANGED
1446
+ CHATS_CHANGED {
1447
+ }
1448
+ @description "A conversation was started, used or reopened."
1449
+ OPEN_CHAT {
1450
+ id : string!
1451
+ row : AgentChat
1452
+ }
1453
+ @description "Reopen this conversation."
1454
+ @synonyms "open, select, switch"
1455
+ @tier "presentation"
1456
+ NEW_CHAT_CLICKED {
1457
+ }
1458
+ @description "Start a new conversation."
1459
+ @tier "presentation"
1460
+ }
1461
+ }
1462
+
1463
+ page "/agent-session/chats" as AgentChatsPage -> AgentChatList, AgentChatKeeper
1464
+ }