@almadar/std 16.242.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.
- package/behaviors/behaviors-registry.json +4470 -513
- package/behaviors/lolo/agent/atoms/std-agent-context.lolo +24 -4
- package/behaviors/lolo/agent/atoms/std-agent-memory.lolo +20 -5
- package/behaviors/lolo/agent/atoms/std-agent-proposal.lolo +550 -0
- package/behaviors/lolo/agent/atoms/std-agent-session.lolo +284 -25
- package/behaviors/lolo/agent/atoms/std-agent-suggestions.lolo +841 -0
- package/behaviors/lolo/agent/atoms/std-agent-tool-loop.lolo +58 -8
- package/behaviors/lolo/agent/organisms/std-agent-assistant.lolo +1 -0
- package/behaviors/lolo/infra/atoms/std-page-search.lolo +3 -1
- package/behaviors/lolo/infra/atoms/std-page-treat.lolo +28 -1
- package/behaviors/lolo/infra/atoms/std-page-watch.lolo +3 -1
- package/behaviors/lolo/ui/avl/atoms/ui-avl-state-machine.lolo +22 -0
- package/behaviors/lolo/ui/avl/atoms/ui-avl-transition-explainer.lolo +175 -0
- package/behaviors/lolo/ui/avl/atoms/ui-module-card.lolo +22 -0
- package/behaviors/lolo/ui/core/atoms/std-app-layout.lolo +23 -4
- package/behaviors/lolo/ui/core/atoms/std-hero.lolo +72 -33
- package/behaviors/lolo/ui/core/molecules/ui-agent-chat-feed.lolo +1 -1
- package/behaviors/lolo/ui/core/molecules/ui-markdown-content.lolo +54 -0
- package/behaviors/lolo/ui/core/molecules/ui-upload-drop-zone.lolo +5 -0
- package/behaviors/lolo/ui/core/organisms/ui-segment-renderer.lolo +127 -3
- package/behaviors/lolo/ui/core/organisms/ui-state-machine-view.lolo +22 -0
- package/behaviors/lolo/ui/core/templates/ui-dashboard-layout.lolo +2 -2
- package/behaviors/lolo/ui/learning/atoms/std-learn-arrangement.lolo +87 -17
- package/behaviors/lolo/ui/learning/atoms/std-learn-classify.lolo +172 -37
- package/behaviors/lolo/ui/learning/atoms/std-learn-coupled-sim.lolo +183 -70
- package/behaviors/lolo/ui/learning/atoms/std-learn-derivation.lolo +78 -13
- package/behaviors/lolo/ui/learning/atoms/std-learn-diagram.lolo +58 -2
- package/behaviors/lolo/ui/learning/atoms/std-learn-doser.lolo +107 -12
- package/behaviors/lolo/ui/learning/atoms/std-learn-estimate.lolo +83 -10
- package/behaviors/lolo/ui/learning/atoms/std-learn-goal-seek.lolo +130 -9
- package/behaviors/lolo/ui/learning/atoms/std-learn-grid-sim.lolo +143 -59
- package/behaviors/lolo/ui/learning/atoms/std-learn-hotspot.lolo +96 -22
- package/behaviors/lolo/ui/learning/atoms/std-learn-live-sim.lolo +66 -5
- package/behaviors/lolo/ui/learning/atoms/std-learn-predict.lolo +84 -6
- package/behaviors/lolo/ui/learning/atoms/std-learn-sandbox.lolo +80 -14
- package/behaviors/lolo/ui/learning/atoms/std-learn-scatter-fit.lolo +99 -5
- package/behaviors/lolo/ui/learning/atoms/std-learn-scenario.lolo +81 -7
- package/behaviors/lolo/ui/learning/atoms/std-learn-speed-drill.lolo +83 -7
- package/behaviors/lolo/ui/learning/atoms/std-learn-stepper.lolo +224 -99
- package/behaviors/lolo/ui/learning/atoms/std-learn-tour-3d.lolo +63 -2
- package/behaviors/lolo/ui/learning/atoms/std-learn-trace-player.lolo +99 -38
- package/behaviors/lolo/ui/learning/atoms/std-learn-transport.lolo +7 -7
- package/behaviors/lolo/ui/learning/atoms/std-learn-tree.lolo +69 -4
- package/behaviors/lolo-behavior-contracts.json +1 -1
- package/behaviors/registry/agent/atoms/std-agent-context.orb +205 -6
- package/behaviors/registry/agent/atoms/std-agent-memory.orb +146 -4
- package/behaviors/registry/agent/atoms/std-agent-proposal.orb +1972 -0
- package/behaviors/registry/agent/atoms/std-agent-session.orb +2021 -597
- package/behaviors/registry/agent/atoms/std-agent-suggestions.orb +5079 -0
- package/behaviors/registry/agent/atoms/std-agent-tool-loop.orb +287 -5
- package/behaviors/registry/agent/organisms/std-agent-assistant.orb +7 -0
- package/behaviors/registry/factory-signatures.json +1 -1
- package/behaviors/registry/infra/atoms/std-page-search.orb +19 -1
- package/behaviors/registry/infra/atoms/std-page-treat.orb +327 -2
- package/behaviors/registry/infra/atoms/std-page-watch.orb +19 -1
- package/behaviors/registry/ui/avl/atoms/ui-avl-state-machine.orb +134 -0
- package/behaviors/registry/ui/avl/atoms/ui-avl-transition-explainer.orb +678 -0
- package/behaviors/registry/ui/avl/atoms/ui-module-card.orb +122 -0
- package/behaviors/registry/ui/core/atoms/std-app-layout.orb +73 -4
- package/behaviors/registry/ui/core/atoms/std-hero.orb +174 -97
- package/behaviors/registry/ui/core/molecules/ui-markdown-content.orb +204 -1
- package/behaviors/registry/ui/core/molecules/ui-upload-drop-zone.orb +12 -0
- package/behaviors/registry/ui/core/organisms/ui-segment-renderer.orb +683 -30
- package/behaviors/registry/ui/learning/atoms/std-learn-arrangement.orb +748 -21
- package/behaviors/registry/ui/learning/atoms/std-learn-classify.orb +1429 -41
- package/behaviors/registry/ui/learning/atoms/std-learn-coupled-sim.orb +1031 -185
- package/behaviors/registry/ui/learning/atoms/std-learn-derivation.orb +807 -132
- package/behaviors/registry/ui/learning/atoms/std-learn-diagram.orb +553 -6
- package/behaviors/registry/ui/learning/atoms/std-learn-doser.orb +987 -21
- package/behaviors/registry/ui/learning/atoms/std-learn-estimate.orb +582 -16
- package/behaviors/registry/ui/learning/atoms/std-learn-goal-seek.orb +954 -13
- package/behaviors/registry/ui/learning/atoms/std-learn-grid-sim.orb +828 -88
- package/behaviors/registry/ui/learning/atoms/std-learn-hotspot.orb +730 -28
- package/behaviors/registry/ui/learning/atoms/std-learn-live-sim.orb +479 -9
- package/behaviors/registry/ui/learning/atoms/std-learn-predict.orb +797 -46
- package/behaviors/registry/ui/learning/atoms/std-learn-sandbox.orb +757 -58
- package/behaviors/registry/ui/learning/atoms/std-learn-scatter-fit.orb +1176 -164
- package/behaviors/registry/ui/learning/atoms/std-learn-scenario.orb +599 -12
- package/behaviors/registry/ui/learning/atoms/std-learn-speed-drill.orb +832 -150
- package/behaviors/registry/ui/learning/atoms/std-learn-stepper.orb +1561 -127
- package/behaviors/registry/ui/learning/atoms/std-learn-tour-3d.orb +655 -6
- package/behaviors/registry/ui/learning/atoms/std-learn-trace-player.orb +634 -43
- package/behaviors/registry/ui/learning/atoms/std-learn-transport.orb +85 -31
- package/behaviors/registry/ui/learning/atoms/std-learn-tree.orb +425 -9
- package/canonical-operators.json +45 -2
- package/dist/behaviors/exports-reader.js +16667 -7868
- package/dist/behaviors/functions/index.d.ts +711 -32
- package/dist/behaviors/functions/index.js +16651 -7869
- package/dist/behaviors/index.d.ts +1 -1
- package/dist/behaviors/index.js +16668 -7869
- package/dist/behaviors/query.js +16667 -7868
- package/dist/behaviors-embeddings.hash.json +3 -3
- package/dist/behaviors-embeddings.json +77 -53
- package/dist/behaviors-registry.json +4470 -513
- package/dist/index.d.ts +3 -3
- package/dist/index.js +16101 -7275
- package/dist/knob-embeddings.hash.json +3 -3
- package/dist/knob-embeddings.json +1309 -685
- package/dist/modules/anim.d.ts +1 -1
- package/dist/modules/array.d.ts +1 -1
- package/dist/modules/async.d.ts +1 -1
- package/dist/modules/behavior.d.ts +1 -1
- package/dist/modules/browser.d.ts +1 -1
- package/dist/modules/composition.d.ts +1 -1
- package/dist/modules/contract.d.ts +1 -1
- package/dist/modules/core.d.ts +1 -1
- package/dist/modules/core.js +29 -2
- package/dist/modules/data.d.ts +1 -1
- package/dist/modules/ease.d.ts +1 -1
- package/dist/modules/format.d.ts +1 -1
- package/dist/modules/geo.d.ts +1 -1
- package/dist/modules/graph.d.ts +1 -1
- package/dist/modules/grid.d.ts +1 -1
- package/dist/modules/i18n.d.ts +1 -1
- package/dist/modules/index.d.ts +1 -1
- package/dist/modules/index.js +29 -2
- package/dist/modules/integration.d.ts +1 -1
- package/dist/modules/json.d.ts +1 -1
- package/dist/modules/llm.d.ts +1 -1
- package/dist/modules/math.d.ts +1 -1
- package/dist/modules/memory.d.ts +1 -1
- package/dist/modules/nn.d.ts +1 -1
- package/dist/modules/noise.d.ts +1 -1
- package/dist/modules/object.d.ts +1 -1
- package/dist/modules/os.d.ts +1 -1
- package/dist/modules/path.d.ts +1 -1
- package/dist/modules/prob.d.ts +1 -1
- package/dist/modules/session.d.ts +1 -1
- package/dist/modules/str.d.ts +1 -1
- package/dist/modules/tensor.d.ts +1 -1
- package/dist/modules/time.d.ts +1 -1
- package/dist/modules/trace.d.ts +1 -1
- package/dist/modules/train.d.ts +1 -1
- package/dist/modules/validate.d.ts +1 -1
- package/dist/modules/vector.d.ts +1 -1
- package/dist/modules/workspace.d.ts +1 -1
- package/dist/registry/factory-signatures.json +1 -1
- package/dist/registry.d.ts +2 -2
- package/dist/registry.js +29 -2
- package/dist/{types-Cdb321uM.d.ts → types-Y-yYWEOX.d.ts} +1 -1
- package/i18n/ar.json +3 -1
- package/i18n/en.json +2 -0
- package/i18n/sl.json +3 -1
- 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
|
+
}
|