@adminforth/agent 2.0.10 → 2.2.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 (201) hide show
  1. package/README.md +333 -10
  2. package/dist/application/ports.d.ts +66 -0
  3. package/dist/application/runTurnUseCase.d.ts +71 -0
  4. package/dist/application/runTurnUseCase.js +364 -0
  5. package/dist/custom/ChatFooter.vue +58 -30
  6. package/dist/custom/SteerDebugPanel.vue +45 -0
  7. package/dist/custom/SteerQueue.vue +54 -0
  8. package/dist/custom/composables/agentStore/constants.ts +1 -0
  9. package/dist/custom/composables/agentStore/useAgentSessions.ts +35 -16
  10. package/dist/custom/composables/agentStore/useAgentSteerQueue.ts +114 -0
  11. package/dist/custom/composables/useAgentStore.ts +46 -1
  12. package/dist/custom/conversation_area/MessageRenderer.vue +7 -1
  13. package/dist/custom/speech_recognition_frontend/AudioLines.vue +1 -1
  14. package/dist/custom/speech_recognition_frontend/MicrophoneButon.vue +3 -3
  15. package/dist/{agentEvents.d.ts → domain/agentEvents.d.ts} +5 -1
  16. package/dist/domain/steerBuffer.d.ts +33 -0
  17. package/dist/domain/steerBuffer.js +51 -0
  18. package/dist/{agent → domain}/systemPrompt.js +1 -1
  19. package/dist/{agent/turn → domain}/turnTypes.d.ts +41 -37
  20. package/dist/{agent/turn/VegaLiteStreamBuffer.d.ts → domain/vegaLiteStreamBuffer.d.ts} +1 -1
  21. package/dist/index.d.ts +4 -2
  22. package/dist/index.js +49 -35
  23. package/dist/{agent/runtime/AgentContext.d.ts → llm/agentContext.d.ts} +3 -3
  24. package/dist/{agent/simpleAgent.d.ts → llm/agentModels.d.ts} +18 -16
  25. package/dist/llm/agentModels.js +65 -0
  26. package/dist/{agent/runtime/AgentRuntime.d.ts → llm/agentRuntime.d.ts} +14 -2
  27. package/dist/{agent/runtime/AgentRuntime.js → llm/agentRuntime.js} +41 -6
  28. package/dist/llm/langGraphLlm.d.ts +25 -0
  29. package/dist/llm/langGraphLlm.js +54 -0
  30. package/dist/{agent/middleware/apiBasedTools.d.ts → llm/middleware/apiToolsMiddleware.d.ts} +1 -1
  31. package/dist/{agent/middleware/apiBasedTools.js → llm/middleware/apiToolsMiddleware.js} +8 -14
  32. package/dist/{agent → llm}/middleware/sequenceDebug.d.ts +1 -1
  33. package/dist/llm/middleware/steerMiddleware.d.ts +21 -0
  34. package/dist/llm/middleware/steerMiddleware.js +50 -0
  35. package/dist/{agent/models/AgentModelFactory.d.ts → llm/modelFactory.d.ts} +1 -1
  36. package/dist/{agent/models/AgentModelFactory.js → llm/modelFactory.js} +1 -1
  37. package/dist/llm/streamAdapter.d.ts +18 -0
  38. package/dist/llm/streamAdapter.js +117 -0
  39. package/dist/{sessionStore.d.ts → persistence/sessionStore.d.ts} +19 -2
  40. package/dist/{sessionStore.js → persistence/sessionStore.js} +53 -0
  41. package/dist/{agent/tools/AgentToolProvider.d.ts → tools/agentToolProvider.d.ts} +1 -1
  42. package/dist/{agent/tools/AgentToolProvider.js → tools/agentToolProvider.js} +1 -1
  43. package/dist/{apiBasedTools.js → tools/apiBasedTools.js} +0 -3
  44. package/dist/{agent/tools → tools}/apiTool.d.ts +1 -1
  45. package/dist/{agent/tools → tools}/fetchSkill.js +1 -1
  46. package/dist/{agent/tools → tools}/fetchToolSchema.d.ts +1 -1
  47. package/dist/{agent/tools → tools}/index.d.ts +1 -1
  48. package/dist/{agent/tools → tools}/navigateUser.d.ts +3 -3
  49. package/dist/{endpoints → transport/http}/context.d.ts +12 -4
  50. package/dist/transport/http/context.js +1 -0
  51. package/dist/{endpoints/core.js → transport/http/coreEndpoints.js} +23 -3
  52. package/dist/{endpoints/sessions.js → transport/http/sessionEndpoints.js} +36 -1
  53. package/dist/transport/sse/sseWriter.d.ts +29 -0
  54. package/dist/{surfaces/web-sse/createSseEventEmitter.js → transport/sse/sseWriter.js} +27 -0
  55. package/dist/{chatSurfaceService.d.ts → transport/surfaces/chatSurfaceService.d.ts} +3 -3
  56. package/dist/{chatSurfaceService.js → transport/surfaces/chatSurfaceService.js} +7 -5
  57. package/dist/{agent/speech/SpeechTurnService.d.ts → transport/surfaces/speechTurnService.d.ts} +1 -1
  58. package/dist/{agent/speech/SpeechTurnService.js → transport/surfaces/speechTurnService.js} +2 -2
  59. package/dist/types.d.ts +10 -4
  60. package/package.json +11 -2
  61. package/.woodpecker/buildRelease.sh +0 -13
  62. package/.woodpecker/buildSlackNotify.sh +0 -46
  63. package/.woodpecker/release.yml +0 -57
  64. package/agent/checkpointer.ts +0 -329
  65. package/agent/languageDetect.ts +0 -92
  66. package/agent/middleware/apiBasedTools.ts +0 -174
  67. package/agent/middleware/openAiResponsesContinuation.ts +0 -92
  68. package/agent/middleware/sequenceDebug.ts +0 -429
  69. package/agent/models/AgentModeResolver.ts +0 -9
  70. package/agent/models/AgentModelFactory.ts +0 -28
  71. package/agent/runtime/AgentContext.ts +0 -30
  72. package/agent/runtime/AgentRuntime.ts +0 -89
  73. package/agent/simpleAgent.ts +0 -199
  74. package/agent/skills/registry.ts +0 -162
  75. package/agent/speech/SpeechTurnService.ts +0 -179
  76. package/agent/systemPrompt.ts +0 -133
  77. package/agent/toolCallEvents.ts +0 -135
  78. package/agent/tools/AgentToolProvider.ts +0 -28
  79. package/agent/tools/apiTool.ts +0 -66
  80. package/agent/tools/fetchSkill.ts +0 -68
  81. package/agent/tools/fetchToolSchema.ts +0 -47
  82. package/agent/tools/getUserLocation.ts +0 -45
  83. package/agent/tools/index.ts +0 -31
  84. package/agent/tools/navigateUser.ts +0 -210
  85. package/agent/turn/TurnContextBuilder.ts +0 -36
  86. package/agent/turn/TurnLifecycleService.ts +0 -47
  87. package/agent/turn/TurnPersistenceService.ts +0 -33
  88. package/agent/turn/TurnPromptBuilder.ts +0 -51
  89. package/agent/turn/TurnStreamConsumer.ts +0 -69
  90. package/agent/turn/VegaLiteStreamBuffer.ts +0 -90
  91. package/agent/turn/turnTypes.ts +0 -100
  92. package/agentEvents.ts +0 -75
  93. package/agentTurnService.ts +0 -314
  94. package/apiBasedTools.ts +0 -712
  95. package/build.log +0 -67
  96. package/chatSurfaceService.ts +0 -319
  97. package/custom/ChatFooter.vue +0 -173
  98. package/custom/ChatHeader.vue +0 -78
  99. package/custom/ChatSurface.vue +0 -160
  100. package/custom/CustomAutoScrollContainer.vue +0 -164
  101. package/custom/SessionsHistory.vue +0 -88
  102. package/custom/chat.ts +0 -82
  103. package/custom/composables/agentAudio/utils.ts +0 -220
  104. package/custom/composables/agentStore/constants.ts +0 -19
  105. package/custom/composables/agentStore/pageContext.ts +0 -8
  106. package/custom/composables/agentStore/useAgentChat.ts +0 -241
  107. package/custom/composables/agentStore/useAgentPlaceholder.ts +0 -142
  108. package/custom/composables/agentStore/useAgentSessions.ts +0 -350
  109. package/custom/composables/useAgentAudio.ts +0 -272
  110. package/custom/composables/useAgentStore.ts +0 -510
  111. package/custom/composables/useAgentTransitions.ts +0 -58
  112. package/custom/conversation_area/ConversationArea.vue +0 -301
  113. package/custom/conversation_area/MessageRenderer.vue +0 -49
  114. package/custom/conversation_area/ProcessingTimeline.vue +0 -286
  115. package/custom/conversation_area/ReasoningRenderer.vue +0 -79
  116. package/custom/conversation_area/SystemMessageRenderer.vue +0 -28
  117. package/custom/conversation_area/TextRenderer.vue +0 -182
  118. package/custom/conversation_area/ThreeDotsAnimation.vue +0 -35
  119. package/custom/conversation_area/ToolApprovalRenderer.vue +0 -98
  120. package/custom/conversation_area/ToolRenderer.vue +0 -178
  121. package/custom/conversation_area/ToolsGroup.vue +0 -59
  122. package/custom/env.d.ts +0 -7
  123. package/custom/incremark_code_renderers/IncremarkShikiCodeBlock.vue +0 -393
  124. package/custom/incremark_code_renderers/incremarkCodeHighlight.ts +0 -285
  125. package/custom/incremark_code_renderers/incremarkRenderer.ts +0 -653
  126. package/custom/incremark_code_renderers/renderIncremarkMarkdown.ts +0 -118
  127. package/custom/package.json +0 -30
  128. package/custom/pnpm-lock.yaml +0 -2394
  129. package/custom/public/agentAudio/agent-processing.mp3 +0 -0
  130. package/custom/skills/analyze_data/SKILL.md +0 -25
  131. package/custom/skills/charts/SKILL.md +0 -193
  132. package/custom/skills/fetch_data/SKILL.md +0 -31
  133. package/custom/skills/mutate_data/SKILL.md +0 -121
  134. package/custom/speech_recognition_frontend/AudioLines.vue +0 -43
  135. package/custom/speech_recognition_frontend/MicrophoneButon.vue +0 -185
  136. package/custom/speech_recognition_frontend/types/voice-activity-detection.d.ts +0 -22
  137. package/custom/speech_recognition_frontend/voiceActivityDetection.ts +0 -62
  138. package/custom/tsconfig.json +0 -15
  139. package/custom/types.ts +0 -108
  140. package/custom/utils.ts +0 -38
  141. package/dist/agent/middleware/openAiResponsesContinuation.d.ts +0 -1
  142. package/dist/agent/middleware/openAiResponsesContinuation.js +0 -66
  143. package/dist/agent/models/AgentModeResolver.d.ts +0 -9
  144. package/dist/agent/models/AgentModeResolver.js +0 -9
  145. package/dist/agent/simpleAgent.js +0 -118
  146. package/dist/agent/turn/TurnContextBuilder.d.ts +0 -14
  147. package/dist/agent/turn/TurnContextBuilder.js +0 -31
  148. package/dist/agent/turn/TurnLifecycleService.d.ts +0 -24
  149. package/dist/agent/turn/TurnLifecycleService.js +0 -47
  150. package/dist/agent/turn/TurnPersistenceService.d.ts +0 -13
  151. package/dist/agent/turn/TurnPersistenceService.js +0 -35
  152. package/dist/agent/turn/TurnPromptBuilder.d.ts +0 -19
  153. package/dist/agent/turn/TurnPromptBuilder.js +0 -43
  154. package/dist/agent/turn/TurnStreamConsumer.d.ts +0 -11
  155. package/dist/agent/turn/TurnStreamConsumer.js +0 -84
  156. package/dist/agentTurnService.d.ts +0 -24
  157. package/dist/agentTurnService.js +0 -269
  158. package/dist/surfaces/web-sse/createSseEventEmitter.d.ts +0 -14
  159. package/endpoints/chatSurfaces.ts +0 -66
  160. package/endpoints/context.ts +0 -52
  161. package/endpoints/core.ts +0 -181
  162. package/endpoints/sessions.ts +0 -183
  163. package/errors.ts +0 -10
  164. package/index.ts +0 -192
  165. package/pnpm-workspace.yaml +0 -4
  166. package/sanitizeSpeechText.ts +0 -32
  167. package/sessionStore.ts +0 -105
  168. package/surfaces/web-sse/createSseEventEmitter.ts +0 -311
  169. package/tsconfig.json +0 -14
  170. package/types.ts +0 -127
  171. /package/dist/{agent/turn/turnTypes.js → application/ports.js} +0 -0
  172. /package/dist/{agentEvents.js → domain/agentEvents.js} +0 -0
  173. /package/dist/{agent → domain}/languageDetect.d.ts +0 -0
  174. /package/dist/{agent → domain}/languageDetect.js +0 -0
  175. /package/dist/{agent → domain}/systemPrompt.d.ts +0 -0
  176. /package/dist/{agent → domain}/toolCallEvents.d.ts +0 -0
  177. /package/dist/{agent → domain}/toolCallEvents.js +0 -0
  178. /package/dist/{endpoints/context.js → domain/turnTypes.js} +0 -0
  179. /package/dist/{agent/turn/VegaLiteStreamBuffer.js → domain/vegaLiteStreamBuffer.js} +0 -0
  180. /package/dist/{agent/runtime/AgentContext.js → llm/agentContext.js} +0 -0
  181. /package/dist/{agent → llm}/middleware/sequenceDebug.js +0 -0
  182. /package/dist/{agent/checkpointer.d.ts → persistence/checkpointStore.d.ts} +0 -0
  183. /package/dist/{agent/checkpointer.js → persistence/checkpointStore.js} +0 -0
  184. /package/dist/{errors.d.ts → shared/errors.d.ts} +0 -0
  185. /package/dist/{errors.js → shared/errors.js} +0 -0
  186. /package/dist/{sanitizeSpeechText.d.ts → shared/sanitizeSpeechText.d.ts} +0 -0
  187. /package/dist/{sanitizeSpeechText.js → shared/sanitizeSpeechText.js} +0 -0
  188. /package/dist/{apiBasedTools.d.ts → tools/apiBasedTools.d.ts} +0 -0
  189. /package/dist/{agent/tools → tools}/apiTool.js +0 -0
  190. /package/dist/{agent/tools → tools}/fetchSkill.d.ts +0 -0
  191. /package/dist/{agent/tools → tools}/fetchToolSchema.js +0 -0
  192. /package/dist/{agent/tools → tools}/getUserLocation.d.ts +0 -0
  193. /package/dist/{agent/tools → tools}/getUserLocation.js +0 -0
  194. /package/dist/{agent/tools → tools}/index.js +0 -0
  195. /package/dist/{agent/tools → tools}/navigateUser.js +0 -0
  196. /package/dist/{agent → tools}/skills/registry.d.ts +0 -0
  197. /package/dist/{agent → tools}/skills/registry.js +0 -0
  198. /package/dist/{endpoints/chatSurfaces.d.ts → transport/http/chatSurfaceEndpoints.d.ts} +0 -0
  199. /package/dist/{endpoints/chatSurfaces.js → transport/http/chatSurfaceEndpoints.js} +0 -0
  200. /package/dist/{endpoints/core.d.ts → transport/http/coreEndpoints.d.ts} +0 -0
  201. /package/dist/{endpoints/sessions.d.ts → transport/http/sessionEndpoints.d.ts} +0 -0
@@ -2,7 +2,7 @@ import type { ComputedRef, Ref, ShallowRef } from 'vue';
2
2
  import { callAdminForthApi } from '@/utils';
3
3
  import type { Chat } from '../../chat';
4
4
  import type { IAgentSession, ISessionsListItem, IPart } from '../../types';
5
- import { PRE_SESSION_ID } from './constants';
5
+ import { PRE_SESSION_ID, STEER_PERSIST_PREFIX } from './constants';
6
6
  import { i18nInstance } from '@/i18n';
7
7
 
8
8
  type AdminforthLike = {
@@ -50,14 +50,36 @@ export function createAgentSessionManager({
50
50
 
51
51
  function saveCurrentSessionInCache() {
52
52
  if (currentSession.value) {
53
- currentSession.value.messages = currentChat.value?.messages.map((m: any) => ({
54
- role: m.role,
55
- text: m.parts.map((p: IPart) => p.type === 'text' ? p.text : '').join(''),
56
- })) || [];
53
+ currentSession.value.messages = currentChat.value?.messages.map((m: any) => {
54
+ const text = m.parts.map((p: IPart) => p.type === 'text' ? p.text : '').join('');
55
+ if (m.role === 'user' && m.metadata?.steer) {
56
+ return { role: 'user', text: `${STEER_PERSIST_PREFIX}${text}` };
57
+ }
58
+ return { role: m.role, text };
59
+ }) || [];
57
60
  sessions.value[currentSession.value.sessionId] = currentSession.value;
58
61
  }
59
62
  }
60
63
 
64
+ function mapStoredMessage(m: any): any[] {
65
+ if (m.role === 'user' && typeof m.text === 'string' && m.text.includes(STEER_PERSIST_PREFIX)) {
66
+ const [prompt, ...steers] = m.text.split(STEER_PERSIST_PREFIX);
67
+ const mapped: any[] = [];
68
+ if (prompt) {
69
+ mapped.push({ role: 'user', parts: [{ type: 'text', text: prompt, state: 'done' }] });
70
+ }
71
+ for (const steer of steers) {
72
+ mapped.push({
73
+ role: 'user',
74
+ metadata: { steer: true },
75
+ parts: [{ type: 'text', text: steer, state: 'done' }],
76
+ });
77
+ }
78
+ return mapped;
79
+ }
80
+ return [{ role: m.role, parts: [{ type: 'text', text: m.text, state: 'done' }] }];
81
+ }
82
+
61
83
  async function fetchSession(sessionId: string) {
62
84
  try {
63
85
  const res = await callAdminForthApi({
@@ -85,14 +107,7 @@ export function createAgentSessionManager({
85
107
  currentSession.value = sessions.value[sessionId];
86
108
  setCurrentChat(sessionId);
87
109
  if (currentChat.value.messages.length === 0) {
88
- currentChat.value.messages = currentSession.value?.messages.map((m: any) => ({
89
- role: m.role,
90
- parts:[{
91
- type: 'text',
92
- text: m.text,
93
- state: 'done',
94
- }]
95
- }));
110
+ currentChat.value.messages = currentSession.value?.messages.flatMap(mapStoredMessage);
96
111
  }
97
112
  }
98
113
 
@@ -130,8 +145,10 @@ export function createAgentSessionManager({
130
145
  }
131
146
  }
132
147
 
133
- async function sendMessage() {
134
- const message = trimmedUserMessage.value;
148
+ async function sendMessage(explicitMessage?: string) {
149
+ // `explicitMessage` is used to drain a buffered (queued) message; without it we send
150
+ // whatever is currently typed in the input.
151
+ const message = (explicitMessage ?? trimmedUserMessage.value).trim();
135
152
  if (!message || isMessageInputBlocked.value) {
136
153
  return;
137
154
  }
@@ -147,7 +164,9 @@ export function createAgentSessionManager({
147
164
  currentChat.value?.sendMessage({
148
165
  text: message,
149
166
  });
150
- userMessageInput.value = '';
167
+ if (explicitMessage === undefined) {
168
+ userMessageInput.value = '';
169
+ }
151
170
  }
152
171
 
153
172
  async function createPreSession() {
@@ -0,0 +1,114 @@
1
+ import type { Ref, ShallowRef } from 'vue';
2
+ import { ref } from 'vue';
3
+ import { callAdminForthApi } from '@/utils';
4
+ import type { Chat } from '../../chat';
5
+ import type { IMessage } from '../../types';
6
+ import { PRE_SESSION_ID } from './constants';
7
+
8
+ export type QueuedMessage = {
9
+ id: string;
10
+ text: string;
11
+ };
12
+
13
+ type CreateAgentSteerQueueOptions = {
14
+ activeSessionId: Ref<string | null>;
15
+ currentChat: ShallowRef<Chat<any> | null | undefined>;
16
+ sendMessage: (text: string) => void | Promise<void>;
17
+ };
18
+
19
+ export function createAgentSteerQueue({
20
+ activeSessionId,
21
+ currentChat,
22
+ sendMessage,
23
+ }: CreateAgentSteerQueueOptions) {
24
+ const queue = ref<QueuedMessage[]>([]);
25
+
26
+ function enqueue(text: string) {
27
+ const message = text.trim();
28
+ if (!message) {
29
+ return;
30
+ }
31
+ queue.value.push({ id: crypto.randomUUID(), text: message });
32
+ }
33
+
34
+ function removeQueuedMessage(id: string) {
35
+ queue.value = queue.value.filter((item: QueuedMessage) => item.id !== id);
36
+ }
37
+
38
+ function injectSteerMessage(text: string) {
39
+ const chat = currentChat.value;
40
+ if (!chat) {
41
+ return;
42
+ }
43
+ const steerMessage: IMessage = {
44
+ role: 'user',
45
+ metadata: { steer: true },
46
+ parts: [{ type: 'text', text, state: 'done' }],
47
+ };
48
+ const messages = chat.messages;
49
+ const lastMessage = messages[messages.length - 1];
50
+ if (lastMessage?.role === 'assistant') {
51
+ messages.splice(messages.length - 1, 0, steerMessage);
52
+ } else {
53
+ messages.push(steerMessage);
54
+ }
55
+ }
56
+
57
+ // Persist the steer by appending it onto the running turn's prompt (display only — the
58
+ // LLM already has it via the running turn + checkpointer, so this never re-enters the
59
+ // model context). The backend adds the marker; the reload path splits it back out.
60
+ async function persistSteerMessage(sessionId: string, text: string) {
61
+ try {
62
+ const res = await callAdminForthApi({
63
+ method: 'POST',
64
+ path: '/agent/append-steer-to-turn',
65
+ body: { sessionId, message: text },
66
+ });
67
+ if (res?.error) {
68
+ console.error('Error persisting steer message:', res.error);
69
+ }
70
+ } catch (error) {
71
+ console.error('Error persisting steer message', error);
72
+ }
73
+ }
74
+
75
+ async function steerQueuedMessage(id: string) {
76
+ const item = queue.value.find((candidate: QueuedMessage) => candidate.id === id);
77
+ const sessionId = activeSessionId.value;
78
+ if (!item || !sessionId || sessionId === PRE_SESSION_ID) {
79
+ return;
80
+ }
81
+ removeQueuedMessage(id);
82
+ injectSteerMessage(item.text);
83
+ void persistSteerMessage(sessionId, item.text);
84
+
85
+ try {
86
+ const res = await callAdminForthApi({
87
+ method: 'POST',
88
+ path: '/agent/steer',
89
+ body: { sessionId, message: item.text },
90
+ });
91
+ if (res?.error) {
92
+ console.error('Error steering agent:', res.error);
93
+ }
94
+ } catch (error) {
95
+ console.error('Error steering agent', error);
96
+ }
97
+ }
98
+
99
+ function flushNext() {
100
+ const next = queue.value.shift();
101
+ if (!next) {
102
+ return;
103
+ }
104
+ void sendMessage(next.text);
105
+ }
106
+
107
+ return {
108
+ queue,
109
+ enqueue,
110
+ removeQueuedMessage,
111
+ steerQueuedMessage,
112
+ flushNext,
113
+ };
114
+ }
@@ -18,6 +18,7 @@ import {
18
18
  import { createAgentChatManager } from './agentStore/useAgentChat';
19
19
  import { createAgentPlaceholderController } from './agentStore/useAgentPlaceholder';
20
20
  import { createAgentSessionManager } from './agentStore/useAgentSessions';
21
+ import { createAgentSteerQueue } from './agentStore/useAgentSteerQueue';
21
22
 
22
23
  export const useAgentStore = defineStore('agent', () => {
23
24
  const agentTransitions = useAgentTransitions();
@@ -220,6 +221,42 @@ export const useAgentStore = defineStore('agent', () => {
220
221
  setCurrentChat,
221
222
  });
222
223
 
224
+ // "Turn active" = a request is in flight (submitted or streaming) or paused for HITL
225
+ // approval. Messages submitted during this window are buffered rather than sent.
226
+ const isTurnActive = computed(() => {
227
+ const status = (currentChat.value as any)?.status;
228
+ return status === 'submitted' || status === 'streaming' || hasPendingToolApproval.value;
229
+ });
230
+
231
+ const steerBuffer = createAgentSteerQueue({
232
+ activeSessionId,
233
+ currentChat,
234
+ sendMessage,
235
+ });
236
+
237
+ // Submitting while a turn is active buffers the message (it shows up in the steer
238
+ // queue, where the user can steer it or let it send); otherwise it sends right away.
239
+ function submitUserMessage() {
240
+ const message = trimmedUserMessage.value;
241
+ if (!message) {
242
+ return;
243
+ }
244
+ if (isTurnActive.value) {
245
+ steerBuffer.enqueue(message);
246
+ userMessageInput.value = '';
247
+ return;
248
+ }
249
+ return sendMessage();
250
+ }
251
+
252
+ // When the active turn finishes, drain the next buffered message as a normal send
253
+ // (one per turn, FIFO). Messages the user explicitly steered were already removed.
254
+ watch(isTurnActive, (active: boolean, wasActive: boolean) => {
255
+ if (wasActive && !active) {
256
+ steerBuffer.flushNext();
257
+ }
258
+ });
259
+
223
260
  watch(() => viewportWidth.value, (newWidth) => {
224
261
  if (isFullScreen.value) {
225
262
  setChatWidth(newWidth, false);
@@ -505,6 +542,14 @@ export const useAgentStore = defineStore('agent', () => {
505
542
  addDataToolCallMessage,
506
543
  openAgentPage,
507
544
  setCurrentChatStatus,
508
- updateLastAgentMessage
545
+ updateLastAgentMessage,
546
+ //_________-Steer queue-_____________
547
+ submitUserMessage,
548
+ isTurnActive,
549
+ steerQueue: steerBuffer.queue,
550
+ steerQueuedMessage: steerBuffer.steerQueuedMessage,
551
+ removeQueuedMessage: steerBuffer.removeQueuedMessage,
552
+ enqueueSteerMessage: steerBuffer.enqueue,
553
+ //___________________________________
509
554
  }
510
555
  })
@@ -1,5 +1,11 @@
1
1
  <template>
2
- <ProcessingTimeline
2
+ <div
3
+ v-if="message.role === 'user' && message.metadata?.steer"
4
+ class="self-end mr-4 mb-0.5 text-xs italic text-gray-400 dark:text-gray-500"
5
+ >
6
+ {{ $t('Steering instruction') }}
7
+ </div>
8
+ <ProcessingTimeline
3
9
  :message="message"
4
10
  :isLastMessageInChat="isLastMessageInChat"
5
11
  />
@@ -11,7 +11,7 @@
11
11
  <script setup lang="ts">
12
12
  import { computed } from 'vue';
13
13
 
14
- const IDLE_LINE_HEIGHTS = [0.5, 1, 0.75, 0.5];
14
+ const IDLE_LINE_HEIGHTS = [0.4, 0.8, 0.55, 0.4];
15
15
  const RECORDING_LINE_WEIGHTS = [0.45, 1, 0.75, 0.9, 0.55];
16
16
  const MIN_RECORDING_HEIGHT = 0.25;
17
17
  const MAX_RECORDING_DELTA = 0.9;
@@ -1,7 +1,7 @@
1
1
  <template>
2
2
  <div
3
3
  v-show="!agentStore.isResponseInProgress || isAudioChatMode"
4
- class="absolute bottom-2 flex items-center justify-center z-10 gap-4"
4
+ class="absolute bottom-1 flex items-center justify-center z-10 gap-4"
5
5
  :class="[!agentStore.isAudioChatMode ? 'right-16': agentStore.isMobile ? 'right-1/2 translate-x-1/2' : 'right-1/2 translate-x-1/2']"
6
6
  >
7
7
  <button
@@ -14,10 +14,10 @@
14
14
  </button>
15
15
  <button
16
16
  v-else
17
- class="h-9 bg-lightPrimary dark:bg-darkPrimary
17
+ class="h-7 bg-lightPrimary dark:bg-darkPrimary
18
18
  hover:opacity-90 rounded-full flex items-center justify-center
19
19
  transition-all duration-300 ease-in-out overflow-hidden"
20
- :class="[isAudioChatMode ? 'w-32 px-2': 'w-9']"
20
+ :class="[isAudioChatMode ? 'w-32 px-2': 'w-7']"
21
21
  @click="toggleChatMode"
22
22
  >
23
23
  <div class="w-5 h-5 flex items-center justify-center">
@@ -1,4 +1,4 @@
1
- import type { ToolCallEvent } from "./agent/toolCallEvents.js";
1
+ import type { ToolCallEvent } from "./toolCallEvents.js";
2
2
  export type AgentEvent = {
3
3
  type: "turn-started";
4
4
  messageId: string;
@@ -19,6 +19,10 @@ export type AgentEvent = {
19
19
  type: "interrupt";
20
20
  sessionId: string;
21
21
  interrupt: unknown;
22
+ } | {
23
+ type: "steer-applied";
24
+ count: number;
25
+ ids: string[];
22
26
  } | {
23
27
  type: "open-page";
24
28
  targetPath: string;
@@ -0,0 +1,33 @@
1
+ /** A single steering instruction the user sent while a turn was already running. */
2
+ export type SteerMessage = {
3
+ id: string;
4
+ text: string;
5
+ /** Epoch ms the steer was enqueued; useful for ordering/debugging. */
6
+ at: number;
7
+ };
8
+ /**
9
+ * In-process, per-session buffer of steering instructions.
10
+ *
11
+ * While an agent turn is streaming (thinking / calling tools) the user may send
12
+ * one or more extra instructions ("steers"). Each is enqueued here by the
13
+ * `/agent/steer` endpoint and drained by the `beforeModel` steer middleware just
14
+ * before the next model call in the same running loop, so the model course-corrects
15
+ * mid-turn instead of the user having to abort and restart.
16
+ *
17
+ * The buffer is deliberately in-process: a steer only makes sense for a turn that
18
+ * is *currently running*, and that turn's SSE stream is held open on exactly one
19
+ * instance/process. In a multi-instance deployment the `/agent/steer` request must
20
+ * therefore reach the same instance running the turn (same constraint the in-memory
21
+ * HITL cache / MemorySaver already carry when no persistent checkpointer is set).
22
+ */
23
+ export declare class SteerBuffer {
24
+ private readonly buffers;
25
+ /** Enqueue a steering instruction for a session. Returns the created entry. */
26
+ add(sessionId: string, text: string): SteerMessage;
27
+ /** Number of steers currently buffered for a session. */
28
+ size(sessionId: string): number;
29
+ /** Return every buffered steer for a session and remove them from the buffer. */
30
+ drain(sessionId: string): SteerMessage[];
31
+ /** Discard any buffered steers for a session (e.g. when its turn ends unconsumed). */
32
+ clear(sessionId: string): void;
33
+ }
@@ -0,0 +1,51 @@
1
+ import { randomUUID } from "crypto";
2
+ /**
3
+ * In-process, per-session buffer of steering instructions.
4
+ *
5
+ * While an agent turn is streaming (thinking / calling tools) the user may send
6
+ * one or more extra instructions ("steers"). Each is enqueued here by the
7
+ * `/agent/steer` endpoint and drained by the `beforeModel` steer middleware just
8
+ * before the next model call in the same running loop, so the model course-corrects
9
+ * mid-turn instead of the user having to abort and restart.
10
+ *
11
+ * The buffer is deliberately in-process: a steer only makes sense for a turn that
12
+ * is *currently running*, and that turn's SSE stream is held open on exactly one
13
+ * instance/process. In a multi-instance deployment the `/agent/steer` request must
14
+ * therefore reach the same instance running the turn (same constraint the in-memory
15
+ * HITL cache / MemorySaver already carry when no persistent checkpointer is set).
16
+ */
17
+ export class SteerBuffer {
18
+ constructor() {
19
+ this.buffers = new Map();
20
+ }
21
+ /** Enqueue a steering instruction for a session. Returns the created entry. */
22
+ add(sessionId, text) {
23
+ const message = { id: randomUUID(), text, at: Date.now() };
24
+ const existing = this.buffers.get(sessionId);
25
+ if (existing) {
26
+ existing.push(message);
27
+ }
28
+ else {
29
+ this.buffers.set(sessionId, [message]);
30
+ }
31
+ return message;
32
+ }
33
+ /** Number of steers currently buffered for a session. */
34
+ size(sessionId) {
35
+ var _a, _b;
36
+ return (_b = (_a = this.buffers.get(sessionId)) === null || _a === void 0 ? void 0 : _a.length) !== null && _b !== void 0 ? _b : 0;
37
+ }
38
+ /** Return every buffered steer for a session and remove them from the buffer. */
39
+ drain(sessionId) {
40
+ const messages = this.buffers.get(sessionId);
41
+ if (!messages || messages.length === 0) {
42
+ return [];
43
+ }
44
+ this.buffers.delete(sessionId);
45
+ return messages;
46
+ }
47
+ /** Discard any buffered steers for a session (e.g. when its turn ends unconsumed). */
48
+ clear(sessionId) {
49
+ this.buffers.delete(sessionId);
50
+ }
51
+ }
@@ -7,7 +7,7 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
7
7
  step((generator = generator.apply(thisArg, _arguments || [])).next());
8
8
  });
9
9
  };
10
- import { listProjectSkillManifests, } from "./skills/registry.js";
10
+ import { listProjectSkillManifests, } from "../tools/skills/registry.js";
11
11
  export const DEFAULT_AGENT_SYSTEM_PROMPT = [
12
12
  "You are helpful AI Assistant for Admin Panel.",
13
13
  // about admin
@@ -1,11 +1,21 @@
1
1
  import type { AdminUser, AudioAdapter } from "adminforth";
2
- import type { Messages } from "@langchain/langgraph";
3
- import type { Command } from "@langchain/langgraph";
4
- import type { AgentChatModel, AgentMiddleware } from "../simpleAgent.js";
5
- import type { SequenceDebugCollector } from "../middleware/sequenceDebug.js";
6
- import type { PreviousUserMessage } from "../languageDetect.js";
7
2
  import type { CurrentPageContext } from "../tools/getUserLocation.js";
8
- import type { AgentEventEmitter } from "../../agentEvents.js";
3
+ import type { AgentEventEmitter } from "./agentEvents.js";
4
+ /**
5
+ * Minimal sink the application layer uses to persist per-turn debug traces. The
6
+ * concrete implementation (the sequence-debug collector) lives in the llm layer;
7
+ * the domain/application layers depend only on this narrow interface, so the
8
+ * dependency direction stays llm -> application/domain.
9
+ */
10
+ export type DebugSink = {
11
+ flush(): void;
12
+ getHistory(): unknown[];
13
+ };
14
+ /** A pending human-in-the-loop approval, normalized (provider-agnostic). */
15
+ export type PendingInterrupt = {
16
+ id: string;
17
+ count: number;
18
+ };
9
19
  export type BaseAgentTurnInput = {
10
20
  prompt: string;
11
21
  sessionId: string;
@@ -46,37 +56,7 @@ export type AgentTurnContext = {
46
56
  };
47
57
  export type AgentTurnObservability = {
48
58
  emit?: AgentEventEmitter;
49
- sequenceDebugSink: SequenceDebugCollector;
50
- };
51
- export type PreparedAgentTurn = {
52
- prompt: string;
53
- sessionId: string;
54
- turnId: string;
55
- previousUserMessages: PreviousUserMessage[];
56
- modeName?: string | null;
57
- context: AgentTurnContext;
58
- observability: AgentTurnObservability;
59
- resume?: {
60
- decision: "approve" | "reject";
61
- interrupts?: {
62
- id: string;
63
- count: number;
64
- }[];
65
- };
66
- initialResponse?: string;
67
- };
68
- export type AgentTurnModels = {
69
- model: AgentChatModel;
70
- summaryModel: AgentChatModel;
71
- modelMiddleware?: AgentMiddleware[];
72
- };
73
- export type AgentRuntimeRunInput = {
74
- models: AgentTurnModels;
75
- input: {
76
- messages: Messages;
77
- } | Command;
78
- context: AgentTurnContext;
79
- observability: AgentTurnObservability;
59
+ sequenceDebugSink: DebugSink;
80
60
  };
81
61
  export type RunAndPersistAgentResponseInput = BaseAgentTurnInput & {
82
62
  emit?: AgentEventEmitter;
@@ -92,3 +72,27 @@ export type RunAndPersistAgentResponseResult = {
92
72
  };
93
73
  export type HandleTurnInput = TextAgentTurnInput;
94
74
  export type HandleSpeechTurnInput = SpeechAgentTurnInput;
75
+ /**
76
+ * Provider-agnostic message passed from the application layer to the LLM port.
77
+ * The infrastructure adapter maps it onto concrete LangChain messages.
78
+ */
79
+ export type AgentMessage = {
80
+ role: "system" | "user";
81
+ content: string;
82
+ };
83
+ /**
84
+ * Normalized unit of a streamed agent turn. The LLM port converts the raw
85
+ * LangGraph stream into these typed chunks so the application layer never
86
+ * touches LangGraph's tuple/`any` stream shape.
87
+ */
88
+ export type AgentStreamChunk = {
89
+ kind: "text";
90
+ delta: string;
91
+ } | {
92
+ kind: "reasoning";
93
+ delta: string;
94
+ } | {
95
+ kind: "interrupt";
96
+ interrupt: unknown;
97
+ descriptors: PendingInterrupt[];
98
+ };
@@ -1,4 +1,4 @@
1
- import type { AgentEventEmitter } from "../../agentEvents.js";
1
+ import type { AgentEventEmitter } from "./agentEvents.js";
2
2
  export declare class VegaLiteStreamBuffer {
3
3
  private bufferedTextDelta;
4
4
  private isRenderingVegaLite;
package/dist/index.d.ts CHANGED
@@ -1,7 +1,7 @@
1
1
  import type { AdminForthResource, IAdminForth, IHttpServer } from "adminforth";
2
2
  import { AdminForthPlugin } from "adminforth";
3
3
  import type { PluginOptions } from './types.js';
4
- export type { AgentEvent, AgentEventEmitter } from "./agentEvents.js";
4
+ export type { AgentEvent, AgentEventEmitter } from "./domain/agentEvents.js";
5
5
  export default class AdminForthAgentPlugin extends AdminForthPlugin {
6
6
  options: PluginOptions;
7
7
  agentSystemPromptPromise: Promise<string>;
@@ -9,11 +9,13 @@ export default class AdminForthAgentPlugin extends AdminForthPlugin {
9
9
  private agentSystemPrompt;
10
10
  private checkpointer;
11
11
  private sessionStore;
12
- private agentTurnService;
12
+ private steerBuffer;
13
+ private runTurnUseCase;
13
14
  private speechTurnService;
14
15
  private chatSurfaceService;
15
16
  private getCheckpointer;
16
17
  private getInternalAgentResourceIds;
18
+ private getAgentSystemPrompt;
17
19
  constructor(options: PluginOptions);
18
20
  modifyGlobalConfig(adminforth: IAdminForth): Promise<void>;
19
21
  validateConfigAfterDiscover(adminforth: IAdminForth, resourceConfig: AdminForthResource): void;