@applica-software-guru/persona-chat-sdk 0.1.113 → 0.1.115

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.
@@ -1,6 +1,6 @@
1
1
  import { AppendMessage } from '@assistant-ui/react';
2
2
  import { StartRunConfig } from '@assistant-ui/react/dist/legacy-runtime/runtime-cores/core/ThreadRuntimeCore';
3
- import { PersonaProtocol, PersonaMessage, TransformMessages } from '../types';
3
+ import { PersonaProtocol, PersonaMessage, TransformMessages, AgentState } from '../types';
4
4
  import type { PersonaLogger } from '../logging';
5
5
  import { parseMessages } from '../messages';
6
6
  import { fileToBase64 } from './utils';
@@ -11,7 +11,7 @@ import { fileToBase64 } from './utils';
11
11
  export function createOnNewHandler(
12
12
  protocols: PersonaProtocol[],
13
13
  setMessages: React.Dispatch<React.SetStateAction<PersonaMessage[]>>,
14
- setIsRunning: React.Dispatch<React.SetStateAction<boolean>>,
14
+ setAgentState: React.Dispatch<React.SetStateAction<AgentState>>,
15
15
  transformMessages?: TransformMessages,
16
16
  logger?: PersonaLogger,
17
17
  ) {
@@ -23,7 +23,19 @@ export function createOnNewHandler(
23
23
  throw new Error('Message must contain text or attachments');
24
24
  }
25
25
 
26
- const ready = protocols.some((protocol) => protocol.getName() !== 'transaction' && protocol.status === 'connected');
26
+ const input = textPart?.type === 'text' ? textPart.text ?? '' : '';
27
+
28
+ // Check for steer command: /steer <text>
29
+ if (input.startsWith('/steer ') && !hasAttachments) {
30
+ const steerText = input.slice(7).trim();
31
+ if (steerText) {
32
+ return steerAgent(protocols, steerText, logger);
33
+ }
34
+ }
35
+
36
+ const ready = protocols.some(
37
+ (protocol) => protocol.getName() !== 'transaction' && protocol.status === 'connected',
38
+ );
27
39
 
28
40
  if (!ready) {
29
41
  setMessages((currentConversation) => {
@@ -40,7 +52,6 @@ export function createOnNewHandler(
40
52
  return;
41
53
  }
42
54
 
43
- const input = textPart?.type === 'text' ? textPart.text ?? '' : '';
44
55
  const userMessage: PersonaMessage = {
45
56
  role: 'user',
46
57
  type: 'text',
@@ -53,9 +64,11 @@ export function createOnNewHandler(
53
64
  return transformMessages ? transformMessages(newMessages) : newMessages;
54
65
  });
55
66
 
56
- setIsRunning(true);
67
+ setAgentState('running');
57
68
 
58
- const protocol = protocols.sort((a, b) => b.getPriority() - a.getPriority()).find((protocol) => protocol.status === 'connected');
69
+ const protocol = protocols
70
+ .sort((a, b) => b.getPriority() - a.getPriority())
71
+ .find((protocol) => protocol.status === 'connected');
59
72
 
60
73
  const content: Array<PersonaMessage> = [];
61
74
 
@@ -133,6 +146,36 @@ export function createOnNewHandler(
133
146
  };
134
147
  }
135
148
 
149
+ /**
150
+ * Sends a steer command to all connected protocols.
151
+ */
152
+ async function steerAgent(
153
+ protocols: PersonaProtocol[],
154
+ steerText: string,
155
+ logger?: PersonaLogger,
156
+ ) {
157
+ const connectedProtocols = protocols.filter(
158
+ (p) => p.getName() !== 'transaction' && p.status === 'connected',
159
+ );
160
+
161
+ if (connectedProtocols.length === 0) {
162
+ logger?.warn('No connected protocol available for steer');
163
+ return;
164
+ }
165
+
166
+ logger?.debug('Sending steer command:', steerText);
167
+
168
+ // Send steer to all connected protocols
169
+ await Promise.allSettled(
170
+ connectedProtocols.map((protocol) =>
171
+ protocol.sendPacket({
172
+ type: 'command',
173
+ payload: { command: 'steer', arguments: { text: steerText } },
174
+ }),
175
+ ),
176
+ );
177
+ }
178
+
136
179
  /**
137
180
  * Creates the onEdit handler for editing messages
138
181
  */
@@ -140,7 +183,7 @@ export function createOnEditHandler(
140
183
  protocols: PersonaProtocol[],
141
184
  messages: PersonaMessage[],
142
185
  setMessages: React.Dispatch<React.SetStateAction<PersonaMessage[]>>,
143
- setIsRunning: React.Dispatch<React.SetStateAction<boolean>>,
186
+ setAgentState: React.Dispatch<React.SetStateAction<AgentState>>,
144
187
  transformMessages?: TransformMessages,
145
188
  logger?: PersonaLogger,
146
189
  ) {
@@ -149,7 +192,9 @@ export function createOnEditHandler(
149
192
  throw new Error('Only text messages are supported');
150
193
  }
151
194
 
152
- const protocol = protocols.sort((a, b) => b.getPriority() - a.getPriority()).find((protocol) => protocol.status === 'connected');
195
+ const protocol = protocols
196
+ .sort((a, b) => b.getPriority() - a.getPriority())
197
+ .find((protocol) => protocol.status === 'connected');
153
198
 
154
199
  if (!protocol) {
155
200
  logger?.debug('No protocol available for edit');
@@ -179,7 +224,7 @@ export function createOnEditHandler(
179
224
  const newMessages = parseMessages([...messagesToKeep, editedMessage]);
180
225
  setMessages(transformMessages ? transformMessages(newMessages) : newMessages);
181
226
 
182
- setIsRunning(true);
227
+ setAgentState('running');
183
228
 
184
229
  const content: Array<PersonaMessage> = [
185
230
  {
@@ -202,12 +247,14 @@ export function createOnReloadHandler(
202
247
  protocols: PersonaProtocol[],
203
248
  messages: PersonaMessage[],
204
249
  setMessages: React.Dispatch<React.SetStateAction<PersonaMessage[]>>,
205
- setIsRunning: React.Dispatch<React.SetStateAction<boolean>>,
250
+ setAgentState: React.Dispatch<React.SetStateAction<AgentState>>,
206
251
  transformMessages?: TransformMessages,
207
252
  logger?: PersonaLogger,
208
253
  ) {
209
254
  return async (parentId: string | null, config: StartRunConfig) => {
210
- const protocol = protocols.sort((a, b) => b.getPriority() - a.getPriority()).find((protocol) => protocol.status === 'connected');
255
+ const protocol = protocols
256
+ .sort((a, b) => b.getPriority() - a.getPriority())
257
+ .find((protocol) => protocol.status === 'connected');
211
258
 
212
259
  if (!protocol) {
213
260
  logger?.debug('No protocol available for reload');
@@ -250,7 +297,7 @@ export function createOnReloadHandler(
250
297
  return;
251
298
  }
252
299
 
253
- setIsRunning(true);
300
+ setAgentState('running');
254
301
 
255
302
  const content: Array<PersonaMessage> = [];
256
303
 
@@ -281,39 +328,30 @@ export function createOnReloadHandler(
281
328
  }
282
329
 
283
330
  /**
284
- * Creates the onCancel handler for cancelling message generation
331
+ * Creates the onCancel handler for cancelling agent execution.
332
+ * Sends the dedicated 'cancel' command to all connected protocols.
285
333
  */
286
334
  export function createOnCancelHandler(
287
335
  protocols: PersonaProtocol[],
288
- setMessages: React.Dispatch<React.SetStateAction<PersonaMessage[]>>,
289
- setIsRunning: React.Dispatch<React.SetStateAction<boolean>>,
290
- transformMessages?: TransformMessages,
336
+ setAgentState: React.Dispatch<React.SetStateAction<AgentState>>,
337
+ logger?: PersonaLogger,
291
338
  ) {
292
339
  return () => {
293
- setIsRunning(false);
340
+ setAgentState('cancelling');
294
341
 
295
- setMessages((currentMessages) => {
296
- const lastMessage = currentMessages[currentMessages.length - 1];
297
- if (lastMessage?.role === 'assistant' && !lastMessage.finishReason) {
298
- const updatedMessages = [...currentMessages];
299
- updatedMessages[updatedMessages.length - 1] = {
300
- ...lastMessage,
301
- finishReason: 'stop',
302
- status: { type: 'incomplete' },
303
- };
304
- return transformMessages ? transformMessages(updatedMessages) : updatedMessages;
305
- }
306
- return currentMessages;
307
- });
342
+ logger?.debug('Sending cancel command to all connected protocols');
308
343
 
344
+ // Send the dedicated cancel command to ALL connected protocols
309
345
  protocols.forEach((protocol) => {
310
346
  if (protocol.status === 'connected') {
311
347
  protocol
312
348
  .sendPacket({
313
349
  type: 'command',
314
- payload: { command: 'set_initial_context', arguments: { cancel: true } },
350
+ payload: { command: 'cancel', arguments: {} },
315
351
  })
316
- .catch(() => {});
352
+ .catch((err) => {
353
+ logger?.error('Failed to send cancel command:', err);
354
+ });
317
355
  }
318
356
  });
319
357
 
@@ -1,23 +1,147 @@
1
- import { PersonaPacket, PersonaMessage, PersonaReasoning, PersonaTransaction, PersonaProtocol, TransformMessages } from '../types';
1
+ import {
2
+ PersonaPacket,
3
+ PersonaMessage,
4
+ PersonaReasoning,
5
+ PersonaTransaction,
6
+ PersonaProtocol,
7
+ TransformMessages,
8
+ AgentState,
9
+ AgentEndPayload,
10
+ ToolExecutionStartPayload,
11
+ ToolExecutionUpdatePayload,
12
+ ToolExecutionEndPayload,
13
+ SteerAcceptedPayload,
14
+ SteerProcessedPayload,
15
+ } from '../types';
2
16
  import { parseMessages } from '../messages';
3
17
 
4
18
  /**
5
- * Creates packet listener for a protocol
19
+ * Creates packet listener for a protocol.
20
+ *
21
+ * Uses setAgentState instead of setIsRunning for proper lifecycle management.
22
+ * The send button is only re-enabled after agent_end.
6
23
  */
7
24
  export function createPacketListener(
8
25
  protocol: PersonaProtocol,
9
26
  setMessages: React.Dispatch<React.SetStateAction<PersonaMessage[]>>,
10
- setIsRunning: React.Dispatch<React.SetStateAction<boolean>>,
27
+ setAgentState: React.Dispatch<React.SetStateAction<AgentState>>,
11
28
  protocols: PersonaProtocol[],
12
29
  transformMessages?: TransformMessages,
30
+ onSteerAccepted?: (payload: SteerAcceptedPayload) => void,
31
+ onSteerProcessed?: (payload: SteerProcessedPayload) => void,
13
32
  ) {
14
- return (message: PersonaPacket) => {
15
- if (message.type === 'message') {
16
- handleMessagePacket(message.payload as PersonaMessage, protocol, setMessages, setIsRunning, transformMessages);
17
- } else if (message.type === 'reasoning') {
18
- handleReasoningPacket(message.payload as PersonaReasoning, protocol, setMessages, transformMessages);
19
- } else if (message.type === 'transaction') {
20
- handleTransactionPacket(message.payload as PersonaTransaction, protocol, protocols);
33
+ return (packet: PersonaPacket) => {
34
+ switch (packet.type) {
35
+ // ─── Lifecycle: Agent ───────────────────────────────────────
36
+ case 'agent_start':
37
+ setAgentState('running');
38
+ break;
39
+
40
+ case 'agent_end': {
41
+ const endPayload = packet.payload as unknown as AgentEndPayload;
42
+ setAgentState('idle');
43
+
44
+ // Aggiungi messaggio di sistema per stop reason speciali
45
+ if (endPayload.stopReason === 'cancelled') {
46
+ setMessages((current) => [
47
+ ...current,
48
+ {
49
+ role: 'assistant' as const,
50
+ type: 'text' as const,
51
+ text: '⏹ Task cancelled',
52
+ createdAt: new Date(),
53
+ finishReason: 'stop',
54
+ status: { type: 'incomplete' as const },
55
+ },
56
+ ]);
57
+ } else if (endPayload.stopReason === 'error') {
58
+ setMessages((current) => [
59
+ ...current,
60
+ {
61
+ role: 'assistant' as const,
62
+ type: 'text' as const,
63
+ text: `❌ Error: ${endPayload.errorMessage || 'Unknown error'}`,
64
+ createdAt: new Date(),
65
+ finishReason: 'stop',
66
+ status: { type: 'incomplete' as const },
67
+ },
68
+ ]);
69
+ }
70
+ break;
71
+ }
72
+
73
+ // ─── Lifecycle: Turn ────────────────────────────────────────
74
+ case 'turn_start':
75
+ // Opzionale: indicatore di nuovo turno nell'UI
76
+ break;
77
+
78
+ case 'turn_end':
79
+ // Opzionale: riepilogo turno
80
+ break;
81
+
82
+ // ─── Lifecycle: Tool ────────────────────────────────────────
83
+ case 'tool_execution_start':
84
+ handleToolStart(
85
+ packet.payload as unknown as ToolExecutionStartPayload,
86
+ protocol,
87
+ setMessages,
88
+ );
89
+ break;
90
+
91
+ case 'tool_execution_update':
92
+ handleToolUpdate(
93
+ packet.payload as unknown as ToolExecutionUpdatePayload,
94
+ setMessages,
95
+ );
96
+ break;
97
+
98
+ case 'tool_execution_end':
99
+ handleToolEnd(
100
+ packet.payload as unknown as ToolExecutionEndPayload,
101
+ protocol,
102
+ setMessages,
103
+ );
104
+ break;
105
+
106
+ // ─── Steer ──────────────────────────────────────────────────
107
+ case 'steer_accepted':
108
+ onSteerAccepted?.(packet.payload as unknown as SteerAcceptedPayload);
109
+ break;
110
+
111
+ case 'steer_processed':
112
+ onSteerProcessed?.(packet.payload as unknown as SteerProcessedPayload);
113
+ break;
114
+
115
+ // ─── Messages ───────────────────────────────────────────────
116
+ case 'message':
117
+ handleMessagePacket(
118
+ packet.payload as unknown as PersonaMessage,
119
+ protocol,
120
+ setMessages,
121
+ transformMessages,
122
+ );
123
+ break;
124
+
125
+ case 'reasoning':
126
+ handleReasoningPacket(
127
+ packet.payload as unknown as PersonaReasoning,
128
+ protocol,
129
+ setMessages,
130
+ transformMessages,
131
+ );
132
+ break;
133
+
134
+ case 'transaction':
135
+ handleTransactionPacket(
136
+ packet.payload as unknown as PersonaTransaction,
137
+ protocol,
138
+ protocols,
139
+ );
140
+ break;
141
+
142
+ default:
143
+ // Ignora packet types sconosciuti (forward compatibility)
144
+ break;
21
145
  }
22
146
  };
23
147
  }
@@ -26,7 +150,6 @@ function handleMessagePacket(
26
150
  personaMessage: PersonaMessage,
27
151
  protocol: PersonaProtocol,
28
152
  setMessages: React.Dispatch<React.SetStateAction<PersonaMessage[]>>,
29
- setIsRunning: React.Dispatch<React.SetStateAction<boolean>>,
30
153
  transformMessages?: TransformMessages,
31
154
  ) {
32
155
  // Ensure message has proper timestamp
@@ -34,11 +157,6 @@ function handleMessagePacket(
34
157
  personaMessage.createdAt = new Date();
35
158
  }
36
159
 
37
- // Handle finish reason and update isRunning state
38
- if (personaMessage?.finishReason === 'stop' && !personaMessage?.functionResponse && !personaMessage?.thought) {
39
- setIsRunning(false);
40
- }
41
-
42
160
  // Convert thought to reasoning type
43
161
  if (personaMessage.thought) {
44
162
  personaMessage.type = 'reasoning';
@@ -74,6 +192,60 @@ function handleReasoningPacket(
74
192
  });
75
193
  }
76
194
 
77
- function handleTransactionPacket(transaction: PersonaTransaction, currentProtocol: PersonaProtocol, protocols: PersonaProtocol[]) {
78
- protocols.filter((p) => p !== currentProtocol).forEach((p) => p.onTransaction(transaction));
195
+ function handleTransactionPacket(
196
+ transaction: PersonaTransaction,
197
+ currentProtocol: PersonaProtocol,
198
+ protocols: PersonaProtocol[],
199
+ ) {
200
+ protocols
201
+ .filter((p) => p !== currentProtocol)
202
+ .forEach((p) => p.onTransaction(transaction));
203
+ }
204
+
205
+ // ─── Tool Execution Handlers ──────────────────────────────────────────
206
+
207
+ function handleToolStart(
208
+ payload: ToolExecutionStartPayload,
209
+ protocol: PersonaProtocol,
210
+ _setMessages: React.Dispatch<React.SetStateAction<PersonaMessage[]>>,
211
+ ) {
212
+ // Opzionale: crea un messaggio "tool in esecuzione" visibile nell'UI
213
+ // Per ora, il tool call viene gestito dal flusso functionCalls esistente.
214
+ // Qui possiamo emettere un log o un indicatore.
215
+ if (typeof window !== 'undefined' && (window as any).__PERSONA_DEBUG__) {
216
+ console.debug(
217
+ `[${protocol.getName()}] Tool start: ${payload.toolName}`,
218
+ payload.args,
219
+ );
220
+ }
221
+ }
222
+
223
+ function handleToolUpdate(
224
+ payload: ToolExecutionUpdatePayload,
225
+ _setMessages: React.Dispatch<React.SetStateAction<PersonaMessage[]>>,
226
+ ) {
227
+ // Tool streaming update: aggiorna il risultato parziale
228
+ // Per ora logghiamo; in futuro si può aggiornare un messaggio tool inline
229
+ if (typeof window !== 'undefined' && (window as any).__PERSONA_DEBUG__) {
230
+ console.debug(
231
+ `Tool update: ${payload.toolCallId}`,
232
+ payload.partialResult,
233
+ );
234
+ }
235
+ }
236
+
237
+ function handleToolEnd(
238
+ payload: ToolExecutionEndPayload,
239
+ protocol: PersonaProtocol,
240
+ _setMessages: React.Dispatch<React.SetStateAction<PersonaMessage[]>>,
241
+ ) {
242
+ // Tool execution completata. Il result arriva anche come functionResponse
243
+ // nel flusso messaggi. Qui logghiamo per debug.
244
+ if (typeof window !== 'undefined' && (window as any).__PERSONA_DEBUG__) {
245
+ console.debug(
246
+ `[${protocol.getName()}] Tool end: ${payload.toolCallId}`,
247
+ payload.durationMs ? `(${payload.durationMs}ms)` : '',
248
+ payload.error ? `ERROR: ${payload.error}` : '',
249
+ );
250
+ }
79
251
  }
package/src/runtime.tsx CHANGED
@@ -7,7 +7,7 @@ import {
7
7
  SimpleTextAttachmentAdapter,
8
8
  } from '@assistant-ui/react';
9
9
  import { SimpleFileAttachmentAdapter } from './runtime/file-attachment-adapter';
10
- import { PersonaConfig, PersonaMessage, PersonaResponse, ProtocolStatus, Session, ThreadData } from './types';
10
+ import { PersonaConfig, PersonaMessage, PersonaResponse, ProtocolStatus, Session, ThreadData, AgentState } from './types';
11
11
  import { parseMessages, convertMessage } from './messages';
12
12
  import { initializeProtocols } from './runtime/protocols';
13
13
  import { createPacketListener } from './runtime/listeners';
@@ -23,6 +23,8 @@ export {
23
23
  usePersonaRuntimeWebRTCProtocol,
24
24
  usePersonaRuntimeMessages,
25
25
  usePersonaRenameThread,
26
+ useAgentState,
27
+ useSteerAgent,
26
28
  } from './runtime/context';
27
29
 
28
30
  export type { PersonaMessage, PersonaResponse, ThreadData };
@@ -44,9 +46,12 @@ function PersonaRuntimeProviderInner({
44
46
  onThreadUnarchive,
45
47
  onThreadDelete,
46
48
  enableAttachments = false,
49
+ enableSteer = false,
50
+ onSteerAccepted,
51
+ onSteerProcessed,
47
52
  ...config
48
53
  }: Readonly<PersonaConfig>) {
49
- const [isRunning, setIsRunning] = useState(false);
54
+ const [agentState, setAgentState] = useState<AgentState>('idle');
50
55
  const [messages, setMessages] = useState<PersonaMessage[]>([]);
51
56
  const [protocolsStatus, setProtocolsStatus] = useState<Map<string, ProtocolStatus>>(new Map());
52
57
  const didMount = useRef(false);
@@ -150,13 +155,23 @@ function PersonaRuntimeProviderInner({
150
155
  });
151
156
 
152
157
  // Packet listener
153
- protocol.addPacketListener(createPacketListener(protocol, setMessages, setIsRunning, protocols, transformMessages));
158
+ protocol.addPacketListener(
159
+ createPacketListener(
160
+ protocol,
161
+ setMessages,
162
+ setAgentState,
163
+ protocols,
164
+ transformMessages,
165
+ onSteerAccepted,
166
+ onSteerProcessed,
167
+ ),
168
+ );
154
169
 
155
170
  // Auto-connect if configured
156
171
  if (protocol.autostart && protocol.status === 'disconnected') {
157
172
  logger?.debug(`Auto-connecting protocol: ${protocol.getName()}`);
158
173
  protocol.connect(currentThreadRef.current).catch(() => {});
159
- setIsRunning(false);
174
+ setAgentState('idle');
160
175
  }
161
176
  });
162
177
  // eslint-disable-next-line react-hooks/exhaustive-deps
@@ -235,30 +250,22 @@ function PersonaRuntimeProviderInner({
235
250
  }, [currentThreadId, threads, protocols, sessionStorage, transformMessages, logger]);
236
251
 
237
252
  // Message handlers
238
- const onNew = useCallback(createOnNewHandler(protocols, setMessages, setIsRunning, transformMessages, logger), [
239
- protocols,
240
- transformMessages,
241
- logger,
242
- ]);
253
+ const onNew = useCallback(
254
+ createOnNewHandler(protocols, setMessages, setAgentState, transformMessages, logger),
255
+ [protocols, transformMessages, logger],
256
+ );
243
257
 
244
- const onEdit = useCallback(createOnEditHandler(protocols, messages, setMessages, setIsRunning, transformMessages, logger), [
245
- protocols,
246
- messages,
247
- transformMessages,
248
- logger,
249
- ]);
258
+ const onEdit = useCallback(
259
+ createOnEditHandler(protocols, messages, setMessages, setAgentState, transformMessages, logger),
260
+ [protocols, messages, transformMessages, logger],
261
+ );
250
262
 
251
- const onReload = useCallback(createOnReloadHandler(protocols, messages, setMessages, setIsRunning, transformMessages, logger), [
252
- protocols,
253
- messages,
254
- transformMessages,
255
- logger,
256
- ]);
263
+ const onReload = useCallback(
264
+ createOnReloadHandler(protocols, messages, setMessages, setAgentState, transformMessages, logger),
265
+ [protocols, messages, transformMessages, logger],
266
+ );
257
267
 
258
- const onCancel = useCallback(createOnCancelHandler(protocols, setMessages, setIsRunning, transformMessages), [
259
- protocols,
260
- transformMessages,
261
- ]);
268
+ const onCancel = useCallback(createOnCancelHandler(protocols, setAgentState, logger), [protocols, logger]);
262
269
 
263
270
  const handleSetMessages = useCallback(
264
271
  (newMessages: readonly PersonaMessage[]) => {
@@ -302,6 +309,7 @@ function PersonaRuntimeProviderInner({
302
309
  ]);
303
310
 
304
311
  // Create runtime
312
+ const isRunning = agentState === 'running' || agentState === 'cancelling';
305
313
  const runtime = useExternalStoreRuntime({
306
314
  isRunning,
307
315
  messages,
@@ -334,8 +342,30 @@ function PersonaRuntimeProviderInner({
334
342
  [sessionStorage, onThreadRename],
335
343
  );
336
344
 
345
+ const steerAgent = useCallback(
346
+ async (text: string) => {
347
+ const connectedProtocols = protocols.filter(
348
+ (p) => p.getName() !== 'transaction' && p.status === 'connected',
349
+ );
350
+ if (connectedProtocols.length === 0) {
351
+ logger?.warn('No connected protocol available for steer');
352
+ return;
353
+ }
354
+ logger?.debug('Sending steer command:', text);
355
+ await Promise.allSettled(
356
+ connectedProtocols.map((protocol) =>
357
+ protocol.sendPacket({
358
+ type: 'command',
359
+ payload: { command: 'steer', arguments: { text } },
360
+ }),
361
+ ),
362
+ );
363
+ },
364
+ [protocols, logger],
365
+ );
366
+
337
367
  return (
338
- <PersonaRuntimeContext.Provider value={{ protocols, protocolsStatus, getMessages, renameThread }}>
368
+ <PersonaRuntimeContext.Provider value={{ protocols, protocolsStatus, agentState, getMessages, renameThread, steerAgent }}>
339
369
  <AssistantRuntimeProvider runtime={runtime}>{children}</AssistantRuntimeProvider>
340
370
  </PersonaRuntimeContext.Provider>
341
371
  );
package/src/types.ts CHANGED
@@ -103,16 +103,25 @@ export type PersonaSource = {
103
103
  };
104
104
 
105
105
  export type PersonaCommand = {
106
- command: 'get_mcp_assets' | 'set_initial_context' | 'set_local_tools';
106
+ command: 'get_mcp_assets' | 'set_initial_context' | 'set_local_tools' | 'cancel' | 'steer' | 'get_agent_state';
107
107
  arguments: Record<string, unknown>;
108
108
  };
109
109
 
110
110
  export type PersonaRequest = PersonaMessage | Array<PersonaMessage> | string;
111
111
 
112
- export type PersonaPacketPayload = PersonaRequest | PersonaCommand | PersonaReasoning | PersonaTransaction;
112
+ export type PersonaPacketPayload = PersonaRequest | PersonaCommand | PersonaReasoning | PersonaTransaction
113
+ | AgentStartPayload | AgentEndPayload
114
+ | TurnStartPayload | TurnEndPayload
115
+ | ToolExecutionStartPayload | ToolExecutionUpdatePayload | ToolExecutionEndPayload
116
+ | SteerAcceptedPayload | SteerProcessedPayload
117
+ | Record<string, unknown>;
113
118
 
114
119
  export type PersonaPacket = {
115
- type: 'request' | 'message' | 'command' | 'reasoning' | 'mcp_assets' | 'transaction';
120
+ type: 'request' | 'message' | 'command' | 'reasoning' | 'mcp_assets' | 'transaction'
121
+ | 'agent_start' | 'agent_end'
122
+ | 'turn_start' | 'turn_end'
123
+ | 'tool_execution_start' | 'tool_execution_update' | 'tool_execution_end'
124
+ | 'steer_accepted' | 'steer_processed';
116
125
  payload?: PersonaPacketPayload;
117
126
  };
118
127
 
@@ -139,6 +148,66 @@ export type PersonaMessage = {
139
148
  };
140
149
  };
141
150
 
151
+ // ─── Nuovi tipi per il lifecycle dell'agente ─────────────────────────
152
+
153
+ export type AgentState = 'idle' | 'running' | 'cancelling';
154
+
155
+ export type AgentStartPayload = {
156
+ sessionId: string;
157
+ agentId: string;
158
+ timestamp: string;
159
+ };
160
+
161
+ export type AgentEndPayload = {
162
+ sessionId: string;
163
+ stopReason: 'completed' | 'cancelled' | 'error' | 'max_turns' | 'steered_completion';
164
+ totalTurns: number;
165
+ totalCredits?: number;
166
+ errorMessage?: string;
167
+ };
168
+
169
+ export type TurnStartPayload = {
170
+ turnNumber: number;
171
+ timestamp: string;
172
+ };
173
+
174
+ export type TurnEndPayload = {
175
+ turnNumber: number;
176
+ messageCount: number;
177
+ toolResults: Array<{ toolCallId: string; toolName: string; result: string }>;
178
+ };
179
+
180
+ export type ToolExecutionStartPayload = {
181
+ toolCallId: string;
182
+ toolName: string;
183
+ args: Record<string, unknown>;
184
+ timestamp: string;
185
+ };
186
+
187
+ export type ToolExecutionUpdatePayload = {
188
+ toolCallId: string;
189
+ partialResult: unknown;
190
+ };
191
+
192
+ export type ToolExecutionEndPayload = {
193
+ toolCallId: string;
194
+ result: unknown;
195
+ durationMs?: number;
196
+ error?: string;
197
+ };
198
+
199
+ export type SteerAcceptedPayload = {
200
+ steerId: string;
201
+ text: string;
202
+ acceptedAt: string;
203
+ willProcessAfterTurn: number | null;
204
+ };
205
+
206
+ export type SteerProcessedPayload = {
207
+ steerId: string;
208
+ processedAtTurn: number;
209
+ };
210
+
142
211
  export type ModelResponse = {
143
212
  messages: PersonaMessage[];
144
213
  };
@@ -283,4 +352,20 @@ export type PersonaConfig = PersonaBaseConfig &
283
352
  * Enable file attachments (images, PDFs, text files) in the composer
284
353
  */
285
354
  enableAttachments?: boolean;
355
+
356
+ /**
357
+ * Enable steer commands during agent execution.
358
+ * When true, the user can send steer instructions while the agent is running.
359
+ */
360
+ enableSteer?: boolean;
361
+
362
+ /**
363
+ * Callback when a steer command is accepted by the server.
364
+ */
365
+ onSteerAccepted?: (payload: SteerAcceptedPayload) => void;
366
+
367
+ /**
368
+ * Callback when a steer command is processed (injected into the agent context).
369
+ */
370
+ onSteerProcessed?: (payload: SteerProcessedPayload) => void;
286
371
  };