@almadar/std 16.243.0 → 16.244.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 (140) hide show
  1. package/behaviors/behaviors-registry.json +4470 -513
  2. package/behaviors/lolo/agent/atoms/std-agent-context.lolo +24 -4
  3. package/behaviors/lolo/agent/atoms/std-agent-memory.lolo +20 -5
  4. package/behaviors/lolo/agent/atoms/std-agent-proposal.lolo +550 -0
  5. package/behaviors/lolo/agent/atoms/std-agent-session.lolo +284 -25
  6. package/behaviors/lolo/agent/atoms/std-agent-suggestions.lolo +841 -0
  7. package/behaviors/lolo/agent/atoms/std-agent-tool-loop.lolo +58 -8
  8. package/behaviors/lolo/agent/organisms/std-agent-assistant.lolo +1 -0
  9. package/behaviors/lolo/infra/atoms/std-page-treat.lolo +28 -1
  10. package/behaviors/lolo/ui/avl/atoms/ui-avl-state-machine.lolo +22 -0
  11. package/behaviors/lolo/ui/avl/atoms/ui-avl-transition-explainer.lolo +175 -0
  12. package/behaviors/lolo/ui/avl/atoms/ui-module-card.lolo +22 -0
  13. package/behaviors/lolo/ui/core/atoms/std-app-layout.lolo +23 -4
  14. package/behaviors/lolo/ui/core/atoms/std-hero.lolo +72 -33
  15. package/behaviors/lolo/ui/core/molecules/ui-agent-chat-feed.lolo +1 -1
  16. package/behaviors/lolo/ui/core/molecules/ui-markdown-content.lolo +54 -0
  17. package/behaviors/lolo/ui/core/molecules/ui-upload-drop-zone.lolo +5 -0
  18. package/behaviors/lolo/ui/core/organisms/ui-segment-renderer.lolo +127 -3
  19. package/behaviors/lolo/ui/core/organisms/ui-state-machine-view.lolo +22 -0
  20. package/behaviors/lolo/ui/core/templates/ui-dashboard-layout.lolo +2 -2
  21. package/behaviors/lolo/ui/learning/atoms/std-learn-arrangement.lolo +87 -17
  22. package/behaviors/lolo/ui/learning/atoms/std-learn-classify.lolo +172 -37
  23. package/behaviors/lolo/ui/learning/atoms/std-learn-coupled-sim.lolo +183 -70
  24. package/behaviors/lolo/ui/learning/atoms/std-learn-derivation.lolo +78 -13
  25. package/behaviors/lolo/ui/learning/atoms/std-learn-diagram.lolo +58 -2
  26. package/behaviors/lolo/ui/learning/atoms/std-learn-doser.lolo +107 -12
  27. package/behaviors/lolo/ui/learning/atoms/std-learn-estimate.lolo +83 -10
  28. package/behaviors/lolo/ui/learning/atoms/std-learn-goal-seek.lolo +130 -9
  29. package/behaviors/lolo/ui/learning/atoms/std-learn-grid-sim.lolo +143 -59
  30. package/behaviors/lolo/ui/learning/atoms/std-learn-hotspot.lolo +96 -22
  31. package/behaviors/lolo/ui/learning/atoms/std-learn-live-sim.lolo +66 -5
  32. package/behaviors/lolo/ui/learning/atoms/std-learn-predict.lolo +84 -6
  33. package/behaviors/lolo/ui/learning/atoms/std-learn-sandbox.lolo +80 -14
  34. package/behaviors/lolo/ui/learning/atoms/std-learn-scatter-fit.lolo +99 -5
  35. package/behaviors/lolo/ui/learning/atoms/std-learn-scenario.lolo +81 -7
  36. package/behaviors/lolo/ui/learning/atoms/std-learn-speed-drill.lolo +83 -7
  37. package/behaviors/lolo/ui/learning/atoms/std-learn-stepper.lolo +224 -99
  38. package/behaviors/lolo/ui/learning/atoms/std-learn-tour-3d.lolo +63 -2
  39. package/behaviors/lolo/ui/learning/atoms/std-learn-trace-player.lolo +99 -38
  40. package/behaviors/lolo/ui/learning/atoms/std-learn-transport.lolo +7 -7
  41. package/behaviors/lolo/ui/learning/atoms/std-learn-tree.lolo +69 -4
  42. package/behaviors/lolo-behavior-contracts.json +1 -1
  43. package/behaviors/registry/agent/atoms/std-agent-context.orb +205 -6
  44. package/behaviors/registry/agent/atoms/std-agent-memory.orb +146 -4
  45. package/behaviors/registry/agent/atoms/std-agent-proposal.orb +1972 -0
  46. package/behaviors/registry/agent/atoms/std-agent-session.orb +2021 -597
  47. package/behaviors/registry/agent/atoms/std-agent-suggestions.orb +5079 -0
  48. package/behaviors/registry/agent/atoms/std-agent-tool-loop.orb +287 -5
  49. package/behaviors/registry/agent/organisms/std-agent-assistant.orb +7 -0
  50. package/behaviors/registry/factory-signatures.json +1 -1
  51. package/behaviors/registry/infra/atoms/std-page-treat.orb +327 -2
  52. package/behaviors/registry/ui/avl/atoms/ui-avl-state-machine.orb +134 -0
  53. package/behaviors/registry/ui/avl/atoms/ui-avl-transition-explainer.orb +678 -0
  54. package/behaviors/registry/ui/avl/atoms/ui-module-card.orb +122 -0
  55. package/behaviors/registry/ui/core/atoms/std-app-layout.orb +73 -4
  56. package/behaviors/registry/ui/core/atoms/std-hero.orb +174 -97
  57. package/behaviors/registry/ui/core/molecules/ui-markdown-content.orb +204 -1
  58. package/behaviors/registry/ui/core/molecules/ui-upload-drop-zone.orb +12 -0
  59. package/behaviors/registry/ui/core/organisms/ui-segment-renderer.orb +683 -30
  60. package/behaviors/registry/ui/learning/atoms/std-learn-arrangement.orb +748 -21
  61. package/behaviors/registry/ui/learning/atoms/std-learn-classify.orb +1429 -41
  62. package/behaviors/registry/ui/learning/atoms/std-learn-coupled-sim.orb +1031 -185
  63. package/behaviors/registry/ui/learning/atoms/std-learn-derivation.orb +807 -132
  64. package/behaviors/registry/ui/learning/atoms/std-learn-diagram.orb +553 -6
  65. package/behaviors/registry/ui/learning/atoms/std-learn-doser.orb +987 -21
  66. package/behaviors/registry/ui/learning/atoms/std-learn-estimate.orb +582 -16
  67. package/behaviors/registry/ui/learning/atoms/std-learn-goal-seek.orb +954 -13
  68. package/behaviors/registry/ui/learning/atoms/std-learn-grid-sim.orb +828 -88
  69. package/behaviors/registry/ui/learning/atoms/std-learn-hotspot.orb +730 -28
  70. package/behaviors/registry/ui/learning/atoms/std-learn-live-sim.orb +479 -9
  71. package/behaviors/registry/ui/learning/atoms/std-learn-predict.orb +797 -46
  72. package/behaviors/registry/ui/learning/atoms/std-learn-sandbox.orb +757 -58
  73. package/behaviors/registry/ui/learning/atoms/std-learn-scatter-fit.orb +1176 -164
  74. package/behaviors/registry/ui/learning/atoms/std-learn-scenario.orb +599 -12
  75. package/behaviors/registry/ui/learning/atoms/std-learn-speed-drill.orb +832 -150
  76. package/behaviors/registry/ui/learning/atoms/std-learn-stepper.orb +1561 -127
  77. package/behaviors/registry/ui/learning/atoms/std-learn-tour-3d.orb +655 -6
  78. package/behaviors/registry/ui/learning/atoms/std-learn-trace-player.orb +634 -43
  79. package/behaviors/registry/ui/learning/atoms/std-learn-transport.orb +85 -31
  80. package/behaviors/registry/ui/learning/atoms/std-learn-tree.orb +425 -9
  81. package/canonical-operators.json +45 -2
  82. package/dist/behaviors/exports-reader.js +16667 -7868
  83. package/dist/behaviors/functions/index.d.ts +711 -32
  84. package/dist/behaviors/functions/index.js +16651 -7869
  85. package/dist/behaviors/index.d.ts +1 -1
  86. package/dist/behaviors/index.js +16668 -7869
  87. package/dist/behaviors/query.js +16667 -7868
  88. package/dist/behaviors-embeddings.hash.json +3 -3
  89. package/dist/behaviors-embeddings.json +77 -53
  90. package/dist/behaviors-registry.json +4470 -513
  91. package/dist/index.d.ts +3 -3
  92. package/dist/index.js +16101 -7275
  93. package/dist/knob-embeddings.hash.json +3 -3
  94. package/dist/knob-embeddings.json +1309 -685
  95. package/dist/modules/anim.d.ts +1 -1
  96. package/dist/modules/array.d.ts +1 -1
  97. package/dist/modules/async.d.ts +1 -1
  98. package/dist/modules/behavior.d.ts +1 -1
  99. package/dist/modules/browser.d.ts +1 -1
  100. package/dist/modules/composition.d.ts +1 -1
  101. package/dist/modules/contract.d.ts +1 -1
  102. package/dist/modules/core.d.ts +1 -1
  103. package/dist/modules/core.js +29 -2
  104. package/dist/modules/data.d.ts +1 -1
  105. package/dist/modules/ease.d.ts +1 -1
  106. package/dist/modules/format.d.ts +1 -1
  107. package/dist/modules/geo.d.ts +1 -1
  108. package/dist/modules/graph.d.ts +1 -1
  109. package/dist/modules/grid.d.ts +1 -1
  110. package/dist/modules/i18n.d.ts +1 -1
  111. package/dist/modules/index.d.ts +1 -1
  112. package/dist/modules/index.js +29 -2
  113. package/dist/modules/integration.d.ts +1 -1
  114. package/dist/modules/json.d.ts +1 -1
  115. package/dist/modules/llm.d.ts +1 -1
  116. package/dist/modules/math.d.ts +1 -1
  117. package/dist/modules/memory.d.ts +1 -1
  118. package/dist/modules/nn.d.ts +1 -1
  119. package/dist/modules/noise.d.ts +1 -1
  120. package/dist/modules/object.d.ts +1 -1
  121. package/dist/modules/os.d.ts +1 -1
  122. package/dist/modules/path.d.ts +1 -1
  123. package/dist/modules/prob.d.ts +1 -1
  124. package/dist/modules/session.d.ts +1 -1
  125. package/dist/modules/str.d.ts +1 -1
  126. package/dist/modules/tensor.d.ts +1 -1
  127. package/dist/modules/time.d.ts +1 -1
  128. package/dist/modules/trace.d.ts +1 -1
  129. package/dist/modules/train.d.ts +1 -1
  130. package/dist/modules/validate.d.ts +1 -1
  131. package/dist/modules/vector.d.ts +1 -1
  132. package/dist/modules/workspace.d.ts +1 -1
  133. package/dist/registry/factory-signatures.json +1 -1
  134. package/dist/registry.d.ts +2 -2
  135. package/dist/registry.js +29 -2
  136. package/dist/{types-Cdb321uM.d.ts → types-Y-yYWEOX.d.ts} +1 -1
  137. package/i18n/ar.json +3 -1
  138. package/i18n/en.json +2 -0
  139. package/i18n/sl.json +3 -1
  140. package/package.json +3 -3
@@ -0,0 +1,841 @@
1
+ app std-agent-suggestions "1.0.0"
2
+ "std-agent-suggestions — an agent that proactively proposes things to the signed-in person, who accepts or dismisses each one. The agent proposes through one declared input, PROPOSE_SUGGESTION (its @description is the tool text): a kind, a title, a body that gives its reasoning, an optional action (a name the app routes) with a payload, an optional href, and a subject (what it is about). A suggestion's signature is kind:action:subject; a signature that was ever proposed to this person, accepted or dismissed included, is never stored again (SUGGESTION_DUPLICATE), and no more than `maxPending` wait at once (SUGGESTION_LIMIT_REACHED). `kinds` and `actions`, when listed, are the only values accepted (SUGGESTION_REJECTED). The agent runs on its own: any trait emitting SUGGESTIONS_TRIGGERED (or an app event routed into AgentSuggestionTrigger's TRIGGERED by re-binding that trait in the import body with `omit { AgentSuggestionTrigger }` and its own `listens`) asks for a run, and once `cooldownMinutes` have passed since this person's last run a second std-agent-tool-loop answers `prompt` with `tools` as the signed-in person — `quiet`, so it streams no live steps and toasts no failure, and its reply and failure are renamed PROACTIVE_REPLIED / PROACTIVE_FAILED so they never reach the assistant's chat. AgentSuggestionInbox lists the pending suggestions with Accept and Dismiss and an empty state that offers to look again (SUGGESTIONS_ANALYZE_REQUESTED). Accept stores accepted, emits SUGGESTION_ACCEPTED { action, payload, href, … } for the app to route (and navigates to `href` when the suggestion has one); Dismiss stores dismissed and emits SUGGESTION_DISMISSED. The inbox renders into `panelSlot`: `main` inline (the content of a std-panel-launcher), or `modal`/`drawer` opened with OPEN and closed with CLOSE (set std-app-layout's notificationClickEvent, or a topBarActions event, to SUGGESTIONS_OPEN_REQUESTED). Import the orbital once per app: `orbital Suggestions = Suggest.orbitals.AgentSuggestionsOrbital { entity MySuggestion entities { AgentSuggestionRun: MySuggestionRun } config { … } }`, retargeting the records onto owner-scoped entities (`ownerId` a relation to your [identity] roster, with @read/@create/@update); a suggestion's proposer, inbox and trigger read only the rows whose ownerId is the signed-in person. PROPOSE_SUGGESTION's refusals (SUGGESTION_DUPLICATE, SUGGESTION_LIMIT_REACHED, SUGGESTION_REJECTED) are the agent's tool response; no trait listens to them. Suggestion kinds, actions and prompts are the app's vocabulary, declared in config."
3
+ ;; @capabilities "proactive suggestions, agent recommendations, suggestion inbox, next best action, accept or dismiss suggestions, notification bell suggestions, agent proposals"
4
+
5
+ type AgentTool = {
6
+ event : event
7
+ read : string
8
+ scope : "declared"
9
+ }
10
+ type AgentChatMessage = {
11
+ role : "user" | "assistant"
12
+ content : string!
13
+ }
14
+ type SuggestionProposal = {
15
+ kind : string
16
+ title : string
17
+ body : string
18
+ action : string
19
+ href : string
20
+ subject : string
21
+ signature : string
22
+ payload : Map string scalar
23
+ }
24
+
25
+ type SuggestionRow = {
26
+ id : string!
27
+ ownerId : string!
28
+ kind : string
29
+ title : string
30
+ body : string
31
+ action : string
32
+ href : string
33
+ payload : Map string scalar
34
+ subject : string
35
+ signature : string
36
+ status : "pending" | "accepted" | "dismissed"
37
+ createdAt : number
38
+ resolvedAt : number
39
+ }
40
+ type RunRow = {
41
+ id : string!
42
+ ownerId : string!
43
+ ranAt : number
44
+ }
45
+
46
+ orbital AgentSuggestionsOrbital {
47
+ uses Loop from "std/behaviors/std-agent-tool-loop"
48
+
49
+ type SuggestionsLoaded = Event { data : [SuggestionRow] } "Fired when the person's suggestions finish loading"
50
+ type SuggestionsLoadFailed = Event { error : string, code : string } "Fired when the suggestions fail to load"
51
+ type SuggestionResolveFailed = Event { error : string, code : string } "Fired when a suggestion cannot be accepted or dismissed"
52
+ type SuggestionsForPropose = Event { data : [SuggestionRow] } "Fired when the person's suggestions are loaded for a proposal's duplicate check"
53
+ type SuggestionCheckFailed = Event { error : string, code : string } "Fired when a proposal's duplicate check cannot load the person's suggestions"
54
+ type SuggestionSaveFailed = Event { error : string, code : string } "Fired when a proposed suggestion cannot be stored"
55
+ type SuggestionForAccept = Event { data : [SuggestionRow] } "Fired when the suggestion being accepted is loaded"
56
+ type SuggestionForDismiss = Event { data : [SuggestionRow] } "Fired when the suggestion being dismissed is loaded"
57
+ type SuggestionAccepted = Event SuggestionRow "Fired after a suggestion is stored as accepted"
58
+ type SuggestionDismissed = Event SuggestionRow "Fired after a suggestion is stored as dismissed"
59
+ type RunsForTrigger = Event { data : [RunRow] } "Fired when the person's earlier runs are loaded for the cooldown check"
60
+ type RunsLoadFailed = Event { error : string, code : string } "Fired when the earlier runs cannot be loaded"
61
+ type RunRecorded = Event RunRow "Fired after a run is recorded"
62
+ type RunRecordFailed = Event { error : string, code : string } "Fired when a run cannot be recorded"
63
+
64
+ config {
65
+ maxPending : number = 5
66
+ @label "How many suggestions may wait at once?"
67
+ @description "A proposal is refused (SUGGESTION_LIMIT_REACHED) while this many are pending. 0 refuses every proposal."
68
+ @tier "policy"
69
+ kinds : [string] = []
70
+ @label "Which kinds may be proposed?"
71
+ @description "When listed, a proposal of any other kind is refused (SUGGESTION_REJECTED). Empty accepts any kind."
72
+ @tier "domain"
73
+ actions : [string] = []
74
+ @label "Which actions may a suggestion carry?"
75
+ @description "When listed, a proposal carrying any other action, or none, is refused (SUGGESTION_REJECTED). Empty accepts any action."
76
+ @tier "domain"
77
+ cooldownMinutes : number = 60
78
+ @label "Minutes between proactive runs"
79
+ @description "A trigger starts a run only when this long has passed since the person's last run. 0 runs on every trigger."
80
+ @tier "policy"
81
+ prompt : string = "Look at what I have been doing and propose what I could do next, one suggestion at a time."
82
+ @label "What does the agent look into?"
83
+ @description "The request each proactive run answers."
84
+ @tier "domain"
85
+ instructions : string = "You propose next steps to the signed-in person. Look things up with the tools, then call the proposal tool once for each suggestion worth making, with the reason in its body. When a proposal is refused as a duplicate or the pending limit is reached, do not propose again. Propose nothing when nothing is worth suggesting."
86
+ @label "How should the agent behave?"
87
+ @description "Standing instructions sent with every proactive run."
88
+ @tier "domain"
89
+ tools : [AgentTool] = [{ event: AgentSuggestionProposer.PROPOSE_SUGGESTION }]
90
+ @label "What may the agent do?"
91
+ @description "The declared inputs it may fire and the entities it may read, as the signed-in person. An importing app restates the list with the proposal input addressed by its local orbital name (`{ event: Suggestions.AgentSuggestionProposer.PROPOSE_SUGGESTION }`), which the agent can never propose without."
92
+ @tier "domain"
93
+ maxSteps : number = 8
94
+ @label "Model round-trips per run"
95
+ @tier "policy"
96
+ title : string = "Suggestions"
97
+ @label "Inbox heading"
98
+ @tier "presentation"
99
+ icon : string = lightbulb
100
+ @label "Inbox icon"
101
+ @tier "presentation"
102
+ emptyTitle : string = "No suggestions right now"
103
+ @label "Empty inbox title"
104
+ @tier "presentation"
105
+ emptyDescription : string = ""
106
+ @label "Empty inbox hint"
107
+ @tier "presentation"
108
+ analyzeLabel : string = "Look for suggestions"
109
+ @label "Look-again button"
110
+ @description "Shown in the empty inbox; asks the agent to look again."
111
+ @tier "presentation"
112
+ acceptLabel : string = Accept
113
+ @label "Accept button"
114
+ @tier "presentation"
115
+ dismissLabel : string = Dismiss
116
+ @label "Dismiss button"
117
+ @tier "presentation"
118
+ closeLabel : string = Close
119
+ @label "Close button"
120
+ @tier "presentation"
121
+ historyLabel : string = "Past suggestions"
122
+ @label "Past-suggestions link"
123
+ @tier "presentation"
124
+ historyHref : string = "/agent-suggestions/history"
125
+ @label "Where past suggestions are listed"
126
+ @description "The page the inbox links to for accepted and dismissed suggestions; empty hides the link. The history page of an app that imports this orbital is its own page over its retargeted entity."
127
+ @tier "presentation"
128
+ panelSlot : slot = main
129
+ @label "Where the inbox renders"
130
+ @description "`main` for the content of a launcher or page; `modal` or `drawer` for an overlay opened with OPEN (a notification bell)."
131
+ @tier "presentation"
132
+ startOpen : boolean = true
133
+ @label "Show the inbox as soon as it mounts?"
134
+ @description "Off for an overlay that waits for OPEN."
135
+ @tier "presentation"
136
+ }
137
+
138
+ entity AgentSuggestionsHost [runtime] {
139
+ id : string!
140
+ }
141
+
142
+ trait AgentSuggestionProposer -> @rebindable AgentSuggestion [lifecycle] {
143
+ config {
144
+ pendingLimit : number = @config.maxPending
145
+ @label "How many suggestions may wait at once?"
146
+ @description "A proposal is refused (SUGGESTION_LIMIT_REACHED) while this many are pending."
147
+ @synonyms "limit, cap, queue size"
148
+ @tier "policy"
149
+ allowedKinds : [string] = @config.kinds
150
+ @label "Which kinds may be proposed?"
151
+ @description "When listed, any other kind is refused. Empty accepts any."
152
+ @synonyms "types, categories, allowed kinds"
153
+ @tier "domain"
154
+ allowedActions : [string] = @config.actions
155
+ @label "Which actions may a suggestion carry?"
156
+ @description "When listed, any other action is refused. Empty accepts any."
157
+ @synonyms "allowed actions, commands, events"
158
+ @tier "domain"
159
+ }
160
+ initial: ready
161
+
162
+ state ready {
163
+ INIT -> ready
164
+ PROPOSE_SUGGESTION -> ready when (and (> (array/len @config.allowedKinds) 0) (not (array/includes @config.allowedKinds ?kind)))
165
+ (emit SUGGESTION_REJECTED { reason: (str/concat "Unknown kind \"" ?kind "\". Allowed kinds: " (str/join @config.allowedKinds ", ")) })
166
+ PROPOSE_SUGGESTION -> ready when (and (or (= (array/len @config.allowedKinds) 0) (array/includes @config.allowedKinds ?kind)) (> (array/len @config.allowedActions) 0) (not (array/includes @config.allowedActions (str/default ?action ""))))
167
+ (emit SUGGESTION_REJECTED { reason: (str/concat "Unknown action \"" (str/default ?action "") "\". Allowed actions: " (str/join @config.allowedActions ", ")) })
168
+ PROPOSE_SUGGESTION -> ready when (and (or (= (array/len @config.allowedKinds) 0) (array/includes @config.allowedKinds ?kind)) (or (= (array/len @config.allowedActions) 0) (array/includes @config.allowedActions (str/default ?action ""))))
169
+ (set @entity.pendingProposal {
170
+ kind: ?kind
171
+ title: ?title
172
+ body: (str/default ?body "")
173
+ action: (str/default ?action "")
174
+ href: (str/default ?href "")
175
+ subject: ?subject
176
+ signature: (str/concat ?kind ":" (str/default ?action "") ":" ?subject)
177
+ payload: (if ?payload ?payload {})
178
+ })
179
+ (fetch AgentSuggestion {
180
+ filter: ["=", (object/get @entity ownerId), @user.id]
181
+ emit: { failure: SuggestionCheckFailed, success: SuggestionsForPropose }
182
+ })
183
+ SuggestionsForPropose -> ready when (array/some ?data (fn item (= @item.signature @entity.pendingProposal.signature)))
184
+ (emit SUGGESTION_DUPLICATE { signature: @entity.pendingProposal.signature })
185
+ SuggestionsForPropose -> ready when (and (not (array/some ?data (fn item (= @item.signature @entity.pendingProposal.signature)))) (>= (array/count ?data (fn item (= @item.status "pending"))) @config.pendingLimit))
186
+ (emit SUGGESTION_LIMIT_REACHED { maxPending: @config.pendingLimit })
187
+ SuggestionsForPropose -> ready when (and (not (array/some ?data (fn item (= @item.signature @entity.pendingProposal.signature)))) (< (array/count ?data (fn item (= @item.status "pending"))) @config.pendingLimit))
188
+ (persist create AgentSuggestion {
189
+ ownerId: @user.id
190
+ kind: @entity.pendingProposal.kind
191
+ title: @entity.pendingProposal.title
192
+ body: @entity.pendingProposal.body
193
+ action: @entity.pendingProposal.action
194
+ href: @entity.pendingProposal.href
195
+ payload: @entity.pendingProposal.payload
196
+ subject: @entity.pendingProposal.subject
197
+ signature: @entity.pendingProposal.signature
198
+ status: pending
199
+ createdAt: @now
200
+ } {
201
+ emit: { failure: SuggestionSaveFailed, success: SUGGESTION_ADDED }
202
+ })
203
+ SuggestionCheckFailed -> ready
204
+ (emit SUGGESTION_REJECTED { reason: (str/concat "Could not check earlier suggestions: " ?error) })
205
+ (render-ui toast {
206
+ message: ?error
207
+ type: alert
208
+ variant: warning
209
+ })
210
+ SuggestionSaveFailed -> ready
211
+ (emit SUGGESTION_REJECTED { reason: (str/concat "Could not store the suggestion: " ?error) })
212
+ (render-ui toast {
213
+ message: ?error
214
+ type: alert
215
+ variant: warning
216
+ })
217
+ }
218
+
219
+ emits {
220
+ SUGGESTION_ADDED -> external
221
+ @description "A suggestion was stored for the person — payload is the created AgentSuggestion row itself."
222
+ @synonyms "proposed, suggested, new suggestion"
223
+ @tier "domain"
224
+ SUGGESTION_DUPLICATE -> external {
225
+ signature : string!
226
+ }
227
+ @description "This signature was already proposed to the person (pending, accepted or dismissed), so nothing was stored."
228
+ @synonyms "already proposed, repeat, seen before"
229
+ @tier "domain"
230
+ SUGGESTION_LIMIT_REACHED -> external {
231
+ maxPending : number!
232
+ }
233
+ @description "The person already has the most pending suggestions allowed, so nothing was stored."
234
+ @synonyms "too many, queue full, cap"
235
+ @tier "domain"
236
+ SUGGESTION_REJECTED -> external {
237
+ reason : string!
238
+ }
239
+ @description "The proposal was refused (a kind or action not allowed, or it could not be checked or stored); the reason says why."
240
+ @synonyms "refused, invalid, not allowed"
241
+ @tier "domain"
242
+ SuggestionsForPropose -> internal {
243
+ data : [SuggestionRow]
244
+ }
245
+ @tier "internal"
246
+ SuggestionCheckFailed -> internal {
247
+ error : string
248
+ code : string
249
+ }
250
+ @tier "internal"
251
+ SuggestionSaveFailed -> internal {
252
+ error : string
253
+ code : string
254
+ }
255
+ @tier "internal"
256
+ }
257
+
258
+ listens {
259
+ PROPOSE_SUGGESTION -> external {
260
+ kind : string!
261
+ title : string!
262
+ body : string
263
+ action : string
264
+ href : string
265
+ subject : string!
266
+ payload : Map string scalar
267
+ }
268
+ @description "Propose something to the signed-in person: a kind, a title, a body that gives the reason, an optional action with its payload, an optional href, and the subject it is about (an id or slug). A proposal whose kind, action and subject were already proposed to this person is refused, whatever became of the first; so is one past the pending limit."
269
+ @synonyms "suggest, recommend, propose, offer"
270
+ }
271
+ }
272
+
273
+ trait AgentSuggestionInbox -> @rebindable AgentSuggestion [interaction, collection] {
274
+ config {
275
+ heading : string = @config.title
276
+ @label "Inbox heading"
277
+ @description "Heading at the top of the inbox."
278
+ @synonyms "panel title, suggestions title"
279
+ @tier "presentation"
280
+ headingIcon : string = @config.icon
281
+ @label "Inbox icon"
282
+ @description "Lucide icon name shown beside the heading."
283
+ @synonyms "header icon"
284
+ @tier "presentation"
285
+ emptyHeading : string = @config.emptyTitle
286
+ @label "What shows when nothing is pending?"
287
+ @synonyms "empty state, no suggestions"
288
+ @tier "presentation"
289
+ emptyHint : string = @config.emptyDescription
290
+ @label "Hint under the empty heading"
291
+ @tier "presentation"
292
+ lookAgainLabel : string = @config.analyzeLabel
293
+ @label "Look-again button label"
294
+ @description "The empty inbox's button, which asks the agent to look again."
295
+ @synonyms "refresh, find suggestions, analyze"
296
+ @tier "presentation"
297
+ approveLabel : string = @config.acceptLabel
298
+ @label "Accept button label"
299
+ @synonyms "approve, yes, do it"
300
+ @tier "presentation"
301
+ declineLabel : string = @config.dismissLabel
302
+ @label "Dismiss button label"
303
+ @synonyms "reject, no thanks, hide"
304
+ @tier "presentation"
305
+ closeButtonLabel : string = @config.closeLabel
306
+ @label "Close button label"
307
+ @synonyms "back, hide"
308
+ @tier "presentation"
309
+ historyLinkLabel : string = @config.historyLabel
310
+ @label "Past-suggestions link label"
311
+ @synonyms "history, archive, earlier suggestions"
312
+ @tier "presentation"
313
+ historyLink : string = @config.historyHref
314
+ @label "Past-suggestions page"
315
+ @description "Where accepted and dismissed suggestions are listed; empty hides the link."
316
+ @tier "presentation"
317
+ inboxSlot : slot = @config.panelSlot
318
+ @label "Where the inbox renders"
319
+ @description "`main` inline; `modal` or `drawer` for an overlay opened with OPEN."
320
+ @tier "internal"
321
+ showOnMount : boolean = @config.startOpen
322
+ @label "Show the inbox as soon as it mounts?"
323
+ @description "Off for an overlay that waits for OPEN."
324
+ @tier "presentation"
325
+ }
326
+ initial: closed
327
+
328
+ state closed {
329
+ INIT -> open when @config.showOnMount
330
+ (fetch AgentSuggestion {
331
+ filter: ["=", (object/get @entity ownerId), @user.id]
332
+ emit: { failure: SuggestionsLoadFailed, success: SuggestionsLoaded }
333
+ })
334
+ INIT -> closed when (not @config.showOnMount)
335
+ (render-ui @config.inboxSlot null)
336
+ OPEN -> open
337
+ (fetch AgentSuggestion {
338
+ filter: ["=", (object/get @entity ownerId), @user.id]
339
+ emit: { failure: SuggestionsLoadFailed, success: SuggestionsLoaded }
340
+ })
341
+ }
342
+
343
+ state open {
344
+ SuggestionsLoaded -> open
345
+ (render-ui @config.inboxSlot {
346
+ children: [{
347
+ align: center
348
+ children: [{
349
+ align: center
350
+ children: [{ name: @config.headingIcon, size: lg, type: icon }, {
351
+ content: @config.heading
352
+ type: typography
353
+ variant: h3
354
+ }]
355
+ direction: horizontal
356
+ gap: sm
357
+ type: stack
358
+ }, (if (not (= @config.inboxSlot "main")) {
359
+ action: CLOSE
360
+ icon: x
361
+ label: @config.closeButtonLabel
362
+ type: button
363
+ variant: ghost
364
+ } null)]
365
+ direction: horizontal
366
+ gap: sm
367
+ justify: between
368
+ type: stack
369
+ }, { type: divider }, {
370
+ emptyAction: { label: @config.lookAgainLabel, event: ANALYZE }
371
+ emptyDescription: @config.emptyHint
372
+ emptyTitle: @config.emptyHeading
373
+ entity: (array/sort (array/filter ?data (fn item (= @item.status "pending"))) createdAt desc)
374
+ fields: [{
375
+ label: Kind
376
+ name: kind
377
+ variant: badge
378
+ }, {
379
+ label: Suggestion
380
+ name: title
381
+ variant: h4
382
+ }, {
383
+ label: Why
384
+ name: body
385
+ variant: body
386
+ }]
387
+ gap: sm
388
+ itemActions: [{
389
+ event: ACCEPT
390
+ icon: check
391
+ label: @config.approveLabel
392
+ variant: primary
393
+ }, {
394
+ event: DISMISS
395
+ icon: x
396
+ label: @config.declineLabel
397
+ variant: ghost
398
+ }]
399
+ type: data-list
400
+ variant: card
401
+ }, (if (not (= @config.historyLink "")) {
402
+ href: @config.historyLink
403
+ label: @config.historyLinkLabel
404
+ size: sm
405
+ type: button
406
+ variant: ghost
407
+ } null)]
408
+ className: "max-h-[60vh] overflow-y-auto"
409
+ direction: vertical
410
+ gap: md
411
+ type: stack
412
+ })
413
+ SuggestionsLoadFailed -> open
414
+ (render-ui toast {
415
+ message: ?error
416
+ type: alert
417
+ variant: error
418
+ })
419
+ SuggestionResolveFailed -> open
420
+ (render-ui toast {
421
+ message: ?error
422
+ type: alert
423
+ variant: error
424
+ })
425
+ REFRESH -> open
426
+ (fetch AgentSuggestion {
427
+ filter: ["=", (object/get @entity ownerId), @user.id]
428
+ emit: { failure: SuggestionsLoadFailed, success: SuggestionsLoaded }
429
+ })
430
+ OPEN -> open
431
+ (fetch AgentSuggestion {
432
+ filter: ["=", (object/get @entity ownerId), @user.id]
433
+ emit: { failure: SuggestionsLoadFailed, success: SuggestionsLoaded }
434
+ })
435
+ ANALYZE -> open
436
+ (emit SUGGESTIONS_ANALYZE_REQUESTED {})
437
+ ACCEPT -> open
438
+ (fetch AgentSuggestion {
439
+ emit: { failure: SuggestionResolveFailed, success: SuggestionForAccept }
440
+ filter: ["=", (object/get @entity id), ?id]
441
+ })
442
+ SuggestionForAccept -> open when (and (> (array/len ?data) 0) (= (object/get (array/first ?data) status) "pending"))
443
+ (persist update AgentSuggestion {
444
+ id: (object/get (array/first ?data) id)
445
+ ownerId: (object/get (array/first ?data) ownerId)
446
+ kind: (object/get (array/first ?data) kind)
447
+ title: (object/get (array/first ?data) title)
448
+ body: (object/get (array/first ?data) body)
449
+ action: (object/get (array/first ?data) action)
450
+ href: (object/get (array/first ?data) href)
451
+ payload: (object/get (array/first ?data) payload)
452
+ subject: (object/get (array/first ?data) subject)
453
+ signature: (object/get (array/first ?data) signature)
454
+ status: accepted
455
+ createdAt: (object/get (array/first ?data) createdAt)
456
+ resolvedAt: @now
457
+ } {
458
+ emit: { failure: SuggestionResolveFailed, success: SuggestionAccepted }
459
+ })
460
+ SuggestionAccepted -> open when (= (str/default ?href "") "")
461
+ (emit SUGGESTION_ACCEPTED { id: ?id, kind: ?kind, action: ?action, href: ?href, subject: ?subject, payload: ?payload })
462
+ (fetch AgentSuggestion {
463
+ filter: ["=", (object/get @entity ownerId), @user.id]
464
+ emit: { failure: SuggestionsLoadFailed, success: SuggestionsLoaded }
465
+ })
466
+ SuggestionAccepted -> open when (not (= (str/default ?href "") ""))
467
+ (emit SUGGESTION_ACCEPTED { id: ?id, kind: ?kind, action: ?action, href: ?href, subject: ?subject, payload: ?payload })
468
+ (fetch AgentSuggestion {
469
+ filter: ["=", (object/get @entity ownerId), @user.id]
470
+ emit: { failure: SuggestionsLoadFailed, success: SuggestionsLoaded }
471
+ })
472
+ (navigate ?href)
473
+ DISMISS -> open
474
+ (fetch AgentSuggestion {
475
+ emit: { failure: SuggestionResolveFailed, success: SuggestionForDismiss }
476
+ filter: ["=", (object/get @entity id), ?id]
477
+ })
478
+ SuggestionForDismiss -> open when (and (> (array/len ?data) 0) (= (object/get (array/first ?data) status) "pending"))
479
+ (persist update AgentSuggestion {
480
+ id: (object/get (array/first ?data) id)
481
+ ownerId: (object/get (array/first ?data) ownerId)
482
+ kind: (object/get (array/first ?data) kind)
483
+ title: (object/get (array/first ?data) title)
484
+ body: (object/get (array/first ?data) body)
485
+ action: (object/get (array/first ?data) action)
486
+ href: (object/get (array/first ?data) href)
487
+ payload: (object/get (array/first ?data) payload)
488
+ subject: (object/get (array/first ?data) subject)
489
+ signature: (object/get (array/first ?data) signature)
490
+ status: dismissed
491
+ createdAt: (object/get (array/first ?data) createdAt)
492
+ resolvedAt: @now
493
+ } {
494
+ emit: { failure: SuggestionResolveFailed, success: SuggestionDismissed }
495
+ })
496
+ SuggestionDismissed -> open
497
+ (emit SUGGESTION_DISMISSED { id: ?id, kind: ?kind, action: ?action, subject: ?subject })
498
+ (fetch AgentSuggestion {
499
+ filter: ["=", (object/get @entity ownerId), @user.id]
500
+ emit: { failure: SuggestionsLoadFailed, success: SuggestionsLoaded }
501
+ })
502
+ CLOSE -> closed
503
+ (render-ui @config.inboxSlot null)
504
+ }
505
+
506
+ emits {
507
+ SUGGESTION_ACCEPTED -> external {
508
+ id : string!
509
+ kind : string
510
+ action : string
511
+ href : string
512
+ subject : string
513
+ payload : Map string scalar
514
+ }
515
+ @description "The person accepted a suggestion; the app routes `action` with `payload` (a suggestion with an href has already navigated there)."
516
+ @synonyms "accepted, approved, taken up, do it"
517
+ @tier "domain"
518
+ SUGGESTION_DISMISSED -> external {
519
+ id : string!
520
+ kind : string
521
+ action : string
522
+ subject : string
523
+ }
524
+ @description "The person dismissed a suggestion; its signature is never proposed to them again."
525
+ @synonyms "dismissed, rejected, declined, no thanks"
526
+ @tier "domain"
527
+ SUGGESTIONS_ANALYZE_REQUESTED -> external {
528
+ }
529
+ @description "The person asked the agent to look for suggestions again."
530
+ @synonyms "look again, refresh suggestions, analyze"
531
+ @tier "domain"
532
+ SuggestionsLoaded -> internal {
533
+ data : [SuggestionRow]
534
+ }
535
+ @tier "internal"
536
+ SuggestionsLoadFailed -> internal {
537
+ error : string
538
+ code : string
539
+ }
540
+ @tier "internal"
541
+ SuggestionResolveFailed -> internal {
542
+ error : string
543
+ code : string
544
+ }
545
+ @tier "internal"
546
+ SuggestionForAccept -> internal {
547
+ data : [SuggestionRow]
548
+ }
549
+ @tier "internal"
550
+ SuggestionForDismiss -> internal {
551
+ data : [SuggestionRow]
552
+ }
553
+ @tier "internal"
554
+ SuggestionAccepted -> internal
555
+ @tier "internal"
556
+ SuggestionDismissed -> internal
557
+ @tier "internal"
558
+ }
559
+
560
+ listens {
561
+ AgentSuggestionProposer.SUGGESTION_ADDED -> REFRESH
562
+ AgentSuggestionRunner.SUGGESTION_RUN_DONE -> REFRESH
563
+ AgentSuggestionRunner.SUGGESTION_RUN_FAILED -> REFRESH
564
+ *.SUGGESTIONS_OPEN_REQUESTED -> OPEN
565
+ OPEN {
566
+ }
567
+ @description "Show the suggestions inbox. SUGGESTIONS_OPEN_REQUESTED from any trait arrives here."
568
+ @synonyms "show suggestions, open bell, view suggestions"
569
+ @tier "presentation"
570
+ CLOSE {
571
+ }
572
+ @description "Hide the suggestions inbox."
573
+ @synonyms "close, hide, dismiss panel"
574
+ @tier "presentation"
575
+ REFRESH {
576
+ }
577
+ @description "Reload the pending suggestions."
578
+ ANALYZE {
579
+ }
580
+ @description "Ask the agent to look for suggestions again."
581
+ @tier "presentation"
582
+ ACCEPT {
583
+ id : string!
584
+ }
585
+ @description "Accept this suggestion."
586
+ @tier "presentation"
587
+ DISMISS {
588
+ id : string!
589
+ }
590
+ @description "Dismiss this suggestion."
591
+ @tier "presentation"
592
+ }
593
+ }
594
+
595
+ trait AgentSuggestionTrigger -> @rebindable AgentSuggestionRun [lifecycle] {
596
+ config {
597
+ gapMinutes : number = @config.cooldownMinutes
598
+ @label "Minutes between proactive runs"
599
+ @description "A trigger starts a run only when this long has passed since the person's last run."
600
+ @synonyms "throttle, rate limit, interval, frequency"
601
+ @tier "policy"
602
+ }
603
+ initial: ready
604
+
605
+ state ready {
606
+ INIT -> ready
607
+ TRIGGERED -> ready
608
+ (fetch AgentSuggestionRun {
609
+ filter: ["=", (object/get @entity ownerId), @user.id]
610
+ emit: { failure: RunsLoadFailed, success: RunsForTrigger }
611
+ })
612
+ RunsForTrigger -> ready when (>= (- @now (if (= (array/len ?data) 0) 0 (array/max ?data ranAt))) (* @config.gapMinutes 60000))
613
+ (emit SUGGESTION_RUN_ALLOWED {})
614
+ RunsForTrigger -> ready when (< (- @now (if (= (array/len ?data) 0) 0 (array/max ?data ranAt))) (* @config.gapMinutes 60000))
615
+ RunsLoadFailed -> ready
616
+ (render-ui toast {
617
+ message: ?error
618
+ type: alert
619
+ variant: warning
620
+ })
621
+ }
622
+
623
+ emits {
624
+ SUGGESTION_RUN_ALLOWED -> external {
625
+ }
626
+ @description "A trigger arrived and the cooldown has passed, so the agent is asked to look."
627
+ @synonyms "run allowed, cooldown passed"
628
+ @tier "domain"
629
+ RunsForTrigger -> internal {
630
+ data : [RunRow]
631
+ }
632
+ @tier "internal"
633
+ RunsLoadFailed -> internal {
634
+ error : string
635
+ code : string
636
+ }
637
+ @tier "internal"
638
+ }
639
+
640
+ listens {
641
+ *.SUGGESTIONS_TRIGGERED -> TRIGGERED
642
+ TRIGGERED {
643
+ }
644
+ @description "Something happened that is a reason to look for suggestions. SUGGESTIONS_TRIGGERED from any trait arrives here; an app routes its own events with `listens { Source.EVENT -> TRIGGERED }` on a re-bound AgentSuggestionTrigger."
645
+ @synonyms "trigger, start analysis, look for suggestions"
646
+ @tier "domain"
647
+ }
648
+ }
649
+
650
+ trait AgentSuggestionRunner -> @rebindable AgentSuggestionRun [lifecycle] {
651
+ config {
652
+ request : string = @config.prompt
653
+ @label "What does the agent look into?"
654
+ @description "The request each proactive run answers."
655
+ @synonyms "task, goal, question, brief"
656
+ @tier "domain"
657
+ }
658
+ initial: ready
659
+
660
+ state ready {
661
+ INIT -> ready
662
+ START -> starting
663
+ (persist create AgentSuggestionRun {
664
+ ownerId: @user.id
665
+ ranAt: @now
666
+ } {
667
+ emit: { failure: RunRecordFailed, success: RunRecorded }
668
+ })
669
+ }
670
+
671
+ state starting {
672
+ RunRecorded -> analyzing
673
+ (emit SUGGESTION_RUN_STARTED { messages: [{ role: user, content: @config.request }] })
674
+ RunRecordFailed -> ready
675
+ (emit SUGGESTION_RUN_FAILED { error: ?error })
676
+ (render-ui toast {
677
+ message: ?error
678
+ type: alert
679
+ variant: warning
680
+ })
681
+ }
682
+
683
+ state analyzing {
684
+ LOOP_REPLIED -> ready
685
+ (emit SUGGESTION_RUN_DONE {})
686
+ LOOP_FAILED -> ready
687
+ (emit SUGGESTION_RUN_FAILED { error: ?error })
688
+ }
689
+
690
+ emits {
691
+ SUGGESTION_RUN_STARTED -> external {
692
+ messages : [AgentChatMessage]!
693
+ }
694
+ @description "The agent was asked to look for suggestions."
695
+ @synonyms "analysis started, looking"
696
+ @tier "domain"
697
+ SUGGESTION_RUN_DONE -> external {
698
+ }
699
+ @description "The agent finished looking; any suggestions it made are in the inbox."
700
+ @synonyms "analysis done, finished"
701
+ @tier "domain"
702
+ SUGGESTION_RUN_FAILED -> external {
703
+ error : string
704
+ }
705
+ @description "The agent could not finish looking (no toast; the inbox simply has nothing new)."
706
+ @synonyms "analysis failed, error"
707
+ @tier "domain"
708
+ RunRecorded -> internal
709
+ @tier "internal"
710
+ RunRecordFailed -> internal {
711
+ error : string
712
+ code : string
713
+ }
714
+ @tier "internal"
715
+ }
716
+
717
+ listens {
718
+ AgentSuggestionTrigger.SUGGESTION_RUN_ALLOWED -> START
719
+ AgentSuggestionInbox.SUGGESTIONS_ANALYZE_REQUESTED -> START
720
+ AgentSuggestionLoop.PROACTIVE_REPLIED -> LOOP_REPLIED
721
+ AgentSuggestionLoop.PROACTIVE_FAILED -> LOOP_FAILED
722
+ START {
723
+ }
724
+ @description "Ask the agent to look for suggestions now."
725
+ LOOP_REPLIED {
726
+ reply : string
727
+ }
728
+ @description "The agent answered."
729
+ LOOP_FAILED {
730
+ error : string
731
+ }
732
+ @description "The agent could not answer."
733
+ }
734
+ }
735
+
736
+ trait AgentSuggestionLoop = Loop.traits.AgentToolLoop {
737
+ config {
738
+ tools: @config.tools
739
+ instructions: @config.instructions
740
+ maxSteps: @config.maxSteps
741
+ quiet: true
742
+ cancelKey: "agent-suggestions"
743
+ }
744
+ events {
745
+ ASSISTANT_REPLIED: PROACTIVE_REPLIED
746
+ ASSISTANT_REPLY_FAILED: PROACTIVE_FAILED
747
+ }
748
+ listens {
749
+ AgentSuggestionRunner.SUGGESTION_RUN_STARTED -> CONVERSE
750
+ }
751
+ }
752
+
753
+ page "/agent-suggestions" as AgentSuggestionsPage -> AgentSuggestionInbox, AgentSuggestionProposer, AgentSuggestionTrigger, AgentSuggestionRunner, AgentSuggestionLoop
754
+ }
755
+
756
+ ;; The suggestions and run records live outside AgentSuggestionsOrbital, so a consumer
757
+ ;; importing that orbital retargets them onto its own owner-scoped entities:
758
+ ;; `entities { AgentSuggestion: MySuggestion, AgentSuggestionRun: MySuggestionRun }`.
759
+ orbital AgentSuggestionRecordsOrbital {
760
+ entity AgentSuggestion [persistent: agentSuggestions] {
761
+ id : string!
762
+ ownerId : string! @description "Whom this is proposed to (the real @user). The binding organism scopes @read/@create/@update to it." @synonyms "user, owner, account"
763
+ kind : string @description "What sort of suggestion this is; the app's own vocabulary." @synonyms "type, category" = ""
764
+ title : string @description "The suggestion in a line." @synonyms "headline, name" = ""
765
+ body : string @description "Why the agent proposes it." @synonyms "reasoning, reason, description, explanation" = ""
766
+ action : string @description "What accepting it does: a name the app routes from SUGGESTION_ACCEPTED." @synonyms "event, command" = ""
767
+ href : string @description "A page accepting it opens; empty for none." @synonyms "link, url, route" = ""
768
+ payload : Map string scalar @description "What the action needs." @synonyms "params, arguments, data" = {}
769
+ subject : string @description "What the suggestion is about (an id or slug)." @synonyms "target, topic" = ""
770
+ signature : string @description "kind:action:subject. One signature is proposed to a person at most once, whatever became of it." @synonyms "dedup key, fingerprint" = ""
771
+ status : "pending" | "accepted" | "dismissed" @description "Where the suggestion stands." = pending
772
+ createdAt : number @description "When it was proposed (ms)." @synonyms "time, timestamp" = 0
773
+ resolvedAt : number @description "When it was accepted or dismissed (ms)." = 0
774
+ pendingProposal : SuggestionProposal @intrinsic @description "The proposal being checked for a duplicate." = { kind: "", title: "", body: "", action: "", href: "", subject: "", signature: "", payload: {} }
775
+ }
776
+
777
+ entity AgentSuggestionRun [persistent: agentSuggestionRuns] {
778
+ id : string!
779
+ ownerId : string! @description "Whose run this was (the real @user). The binding organism scopes @read/@create to it." @synonyms "user, owner, account"
780
+ ranAt : number @description "When the agent was last asked to look (ms)." @synonyms "time, started" = 0
781
+ }
782
+
783
+ trait AgentSuggestionHistory -> @rebindable AgentSuggestion [interaction, collection] {
784
+ config {
785
+ emptyHeading : string = "Nothing suggested yet"
786
+ @label "What shows when nothing was ever suggested?"
787
+ @tier "presentation"
788
+ }
789
+ initial: browsing
790
+
791
+ state browsing {
792
+ INIT -> browsing
793
+ (fetch AgentSuggestion {
794
+ filter: ["=", (object/get @entity ownerId), @user.id]
795
+ emit: { failure: SuggestionsLoadFailed, success: SuggestionsLoaded }
796
+ })
797
+ (render-ui main { type: skeleton, variant: list, rows: 6 })
798
+ SuggestionsLoaded -> browsing
799
+ (render-ui main {
800
+ emptyTitle: @config.emptyHeading
801
+ entity: (array/sort ?data createdAt desc)
802
+ fields: [{
803
+ label: Status
804
+ name: status
805
+ variant: badge
806
+ }, {
807
+ label: Suggestion
808
+ name: title
809
+ variant: h4
810
+ }, {
811
+ label: Why
812
+ name: body
813
+ variant: body
814
+ }]
815
+ gap: sm
816
+ type: data-list
817
+ variant: card
818
+ })
819
+ SuggestionsLoadFailed -> browsing
820
+ (render-ui main {
821
+ message: ?error
822
+ type: alert
823
+ variant: error
824
+ })
825
+ }
826
+
827
+ emits {
828
+ SuggestionsLoaded -> internal {
829
+ data : [SuggestionRow]
830
+ }
831
+ @tier "internal"
832
+ SuggestionsLoadFailed -> internal {
833
+ error : string
834
+ code : string
835
+ }
836
+ @tier "internal"
837
+ }
838
+ }
839
+
840
+ page "/agent-suggestions/history" as AgentSuggestionHistoryPage -> AgentSuggestionHistory
841
+ }