@datalayer/agent-runtimes 1.5.42 → 1.5.44

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 (83) hide show
  1. package/dist-embed/chunks/1f30a-DxwAt2Ba.js +1 -0
  2. package/dist-embed/chunks/1f310-AqUM9a8q.js +1 -0
  3. package/dist-embed/chunks/1f324-8MHLOhTj.js +1 -0
  4. package/dist-embed/chunks/1f326-YSBr3aXI.js +1 -0
  5. package/dist-embed/chunks/1f327-Ie-nQBNO.js +1 -0
  6. package/dist-embed/chunks/1f33e-Cfl2WM63.js +1 -0
  7. package/dist-embed/chunks/1f372-3XLDlXkE.js +1 -0
  8. package/dist-embed/chunks/1f373-BPUJt2bp.js +1 -0
  9. package/dist-embed/chunks/1f39b-CSNtbAXr.js +1 -0
  10. package/dist-embed/chunks/1f3ab-BUCNQHz1.js +1 -0
  11. package/dist-embed/chunks/1f41b-Dre1z0gP.js +1 -0
  12. package/dist-embed/chunks/1f441-D8yah4C4.js +1 -0
  13. package/dist-embed/chunks/1f465-CPle6Hjq.js +1 -0
  14. package/dist-embed/chunks/1f4b9-aacFyckt.js +1 -0
  15. package/dist-embed/chunks/1f4be-DPAcAU4R.js +1 -0
  16. package/dist-embed/chunks/1f4c1-Ch6aJ0cj.js +1 -0
  17. package/dist-embed/chunks/1f4c2-Dx7BKmLO.js +1 -0
  18. package/dist-embed/chunks/1f4c4-CNvjvC5k.js +1 -0
  19. package/dist-embed/chunks/1f4d3-GTxRgjv7.js +1 -0
  20. package/dist-embed/chunks/1f4d6-COOZY-Q_.js +1 -0
  21. package/dist-embed/chunks/1f4dc-BhoW4cjD.js +1 -0
  22. package/dist-embed/chunks/1f4e1-DezU8ow-.js +1 -0
  23. package/dist-embed/chunks/1f4e2-DsStZsI-.js +1 -0
  24. package/dist-embed/chunks/1f4e7-BBi7c_zx.js +1 -0
  25. package/dist-embed/chunks/1f500-D9xRo-IT.js +1 -0
  26. package/dist-embed/chunks/1f501-CzKeDPcw.js +1 -0
  27. package/dist-embed/chunks/1f504-BYIHMoDP.js +1 -0
  28. package/dist-embed/chunks/1f52d-2QxCLKhU.js +1 -0
  29. package/dist-embed/chunks/1f552-4d0Y0kn7.js +1 -0
  30. package/dist-embed/chunks/1f58b-Cp8F_HBg.js +1 -0
  31. package/dist-embed/chunks/1f5b2-D6BUULuh.js +1 -0
  32. package/dist-embed/chunks/1f5c3-DjkgJLaz.js +1 -0
  33. package/dist-embed/chunks/1f5d1-C4Kpe4HL.js +1 -0
  34. package/dist-embed/chunks/1f5dc-DyLO7aB9.js +1 -0
  35. package/dist-embed/chunks/1f604-CEekcaYE.js +1 -0
  36. package/dist-embed/chunks/1f917-CyyS9fgf.js +1 -0
  37. package/dist-embed/chunks/1f9d1-200d-2696-D9eSB-0q.js +1 -0
  38. package/dist-embed/chunks/1fa79-DHhIZbWB.js +1 -0
  39. package/dist-embed/chunks/1fa9d-DUI9e77m.js +1 -0
  40. package/dist-embed/chunks/1fa9f-10ALsMaD.js +1 -0
  41. package/dist-embed/chunks/23f3-ASjqFsbT.js +1 -0
  42. package/dist-embed/chunks/2611-BnnmLm-K.js +1 -0
  43. package/dist-embed/chunks/2702-CfLyp35r.js +1 -0
  44. package/dist-embed/chunks/270f-CLwo30mk.js +1 -0
  45. package/dist-embed/chunks/2728-Qg6be-6p.js +1 -0
  46. package/dist-embed/chunks/2b1c-Uc1aRwAf.js +1 -0
  47. package/dist-embed/datalayer-app-main.js +67 -67
  48. package/lib/apps/apps/sessionRules.d.ts +14 -1
  49. package/lib/apps/apps/sessionRules.js +68 -2
  50. package/lib/apps/core/canvasParts.d.ts +7 -1
  51. package/lib/apps/plugins/app-rules/AppRulesCard.js +2 -2
  52. package/lib/apps/plugins/canvas-parts/index.d.ts +9 -1
  53. package/lib/apps/plugins/canvas-parts/index.js +28 -0
  54. package/lib/chat/ChatInline.js +43 -22
  55. package/lib/chat/base/ChatBase.js +43 -2
  56. package/lib/components/agents/AgentSummary.d.ts +2 -24
  57. package/lib/components/agents/AgentSummary.js +3 -19
  58. package/lib/hooks/useChat.js +110 -144
  59. package/lib/portable/activity.d.ts +78 -0
  60. package/lib/portable/activity.js +129 -0
  61. package/lib/portable/fixtures.d.ts +7 -0
  62. package/lib/portable/fixtures.js +50 -0
  63. package/lib/portable/index.d.ts +4 -1
  64. package/lib/portable/index.js +7 -1
  65. package/lib/portable/lifecycle.d.ts +77 -0
  66. package/lib/portable/lifecycle.js +135 -0
  67. package/lib/portable/runtimes.d.ts +68 -0
  68. package/lib/portable/runtimes.js +69 -0
  69. package/lib/voice/VoiceMenu.d.ts +2 -1
  70. package/lib/voice/VoiceMenu.js +2 -2
  71. package/lib/voice/chatVoice.d.ts +5 -2
  72. package/lib/voice/chatVoice.js +10 -4
  73. package/lib/voice/choices.d.ts +2 -0
  74. package/lib/voice/choices.js +6 -0
  75. package/lib/voice/index.d.ts +1 -0
  76. package/lib/voice/index.js +1 -0
  77. package/lib/voice/sayAloud.d.ts +25 -0
  78. package/lib/voice/sayAloud.js +34 -0
  79. package/lib/voice/serverHearing.d.ts +34 -2
  80. package/lib/voice/serverHearing.js +234 -6
  81. package/lib/voice/settings.d.ts +12 -0
  82. package/lib/voice/settings.js +9 -0
  83. package/package.json +1 -1
@@ -10,8 +10,11 @@
10
10
  */
11
11
  import { useCallback, useMemo } from 'react';
12
12
  import { useChatStore } from '../stores';
13
- import { createUserMessage, createAssistantMessage, generateMessageId, } from '../types/messages';
14
- import { sanitizeAssistantContent } from '../utils';
13
+ import { createUserMessage, generateMessageId } from '../types/messages';
14
+ import { isToolCallMessage } from '../utils';
15
+ import { BROWSER_LIMITS, emptyConversation, reduceConversation, withPrompt, } from '../portable/conversation';
16
+ import { conversationEventsOf, snapshotOfMessages, } from '../portable/adapterEvents';
17
+ import { conversationDrawer, emptyChatLayer, } from '../chat/base/conversationItems';
15
18
  /**
16
19
  * Main hook for chat functionality
17
20
  *
@@ -56,13 +59,17 @@ export function useChat() {
56
59
  const clearMessages = useChatStore(state => state.clearMessages);
57
60
  const setLoading = useChatStore(state => state.setLoading);
58
61
  const setError = useChatStore(state => state.setError);
62
+ const setMessages = useChatStore(state => state.setMessages);
59
63
  const startStreaming = useChatStore(state => state.startStreaming);
60
- const appendToStream = useChatStore(state => state.appendToStream);
61
64
  const stopStreaming = useChatStore(state => state.stopStreaming);
62
65
  const setSuggestions = useChatStore(state => state.setSuggestions);
63
66
  const getTools = useChatStore(state => state.getTools);
64
67
  /**
65
- * Send a user message and get AI response
68
+ * Send a user message and get the AI's response. The conversation is
69
+ * `reduceConversation`'s (MOBILE M-030), as in ChatBase and on the phone:
70
+ * every adapter event read by `conversationEventsOf`, an inference
71
+ * provider's deltas as AG-UI text events, and the store's messages drawn
72
+ * from it.
66
73
  */
67
74
  const sendMessage = useCallback(async (content, options) => {
68
75
  // Check if we have a way to send messages
@@ -70,102 +77,95 @@ export function useChat() {
70
77
  setError(new Error('No inference provider or protocol adapter configured.'));
71
78
  return;
72
79
  }
73
- // Create and add user message
74
80
  const userMessage = createUserMessage(content);
75
- addMessage(userMessage);
76
- // Clear error and set loading
81
+ const draw = conversationDrawer(words => words);
82
+ let conversation = withPrompt(reduceConversation(emptyConversation(BROWSER_LIMITS), snapshotOfMessages(messages)), userMessage.id, content);
83
+ const show = (next) => {
84
+ conversation = next;
85
+ setMessages(draw(conversation, emptyChatLayer()).filter((item) => !isToolCallMessage(item)));
86
+ };
87
+ const reduce = (events) => {
88
+ if (events.length) {
89
+ show(events.reduce((kept, event) => reduceConversation(kept, event), conversation));
90
+ }
91
+ };
92
+ /** An adapter's event, as the conversation reads it; its failure is the chat's error. */
93
+ const read = (event) => {
94
+ if (event.type === 'error') {
95
+ if (event.error) {
96
+ setError(event.error);
97
+ }
98
+ return;
99
+ }
100
+ try {
101
+ reduce(conversationEventsOf(event, conversation));
102
+ }
103
+ catch (failure) {
104
+ setError(failure instanceof Error ? failure : new Error(String(failure)));
105
+ }
106
+ };
107
+ show(conversation);
77
108
  setError(null);
78
109
  setLoading(true);
110
+ startStreaming(generateMessageId());
79
111
  try {
80
- // Get registered tools
81
112
  const tools = getTools();
82
- // Merge options
83
113
  const requestOptions = {
84
114
  ...options,
85
115
  tools: tools.length > 0 ? tools : undefined,
86
116
  };
87
- // Get all messages for context
117
+ // Every message so far, for context.
88
118
  const allMessages = [...messages, userMessage];
89
- // Create placeholder for assistant message
90
- const assistantMessageId = generateMessageId();
91
- const assistantMessage = createAssistantMessage('');
92
- assistantMessage.id = assistantMessageId;
93
- addMessage(assistantMessage);
94
- // Stream the response
95
- startStreaming(assistantMessageId);
96
- // Use protocol adapter if available (for ag-ui, a2a, acp)
97
- // Otherwise fall back to inference provider (for vercel-ai, OpenAI-compatible)
119
+ // A protocol adapter (ag-ui, a2a, acp) when there is one; else the
120
+ // inference provider (vercel-ai, OpenAI-compatible).
98
121
  if (protocolAdapter) {
99
- // Track pending tool calls for this run
122
+ // The calls this run asks the page's tools for, run once it ends.
100
123
  const pendingToolCalls = new Map();
101
- let currentAssistantContent = '';
102
- // Subscribe to adapter events
103
124
  const unsubscribe = protocolAdapter.subscribe(event => {
104
- switch (event.type) {
105
- case 'message':
106
- // AG-UI sends incremental messages with accumulating content
107
- if (event.message) {
108
- const rawContent = typeof event.message.content === 'string'
109
- ? event.message.content
110
- : '';
111
- currentAssistantContent =
112
- sanitizeAssistantContent(rawContent);
113
- // Update the assistant message with the current content
114
- updateMessage(assistantMessageId, {
115
- content: currentAssistantContent,
116
- toolCalls: event.message.toolCalls,
117
- });
118
- }
119
- break;
120
- case 'tool-call':
121
- // Track tool call for execution after stream completes
122
- if (event.toolCall) {
123
- console.log('[useChat] Tool call received:', event.toolCall.toolCallId, event.toolCall.toolName);
124
- pendingToolCalls.set(event.toolCall.toolCallId, {
125
- toolName: event.toolCall.toolName,
126
- args: event.toolCall.args,
127
- });
128
- }
129
- break;
130
- case 'error':
131
- if (event.error) {
132
- setError(event.error);
133
- }
134
- break;
125
+ if (event.type === 'tool-call' && event.toolCall) {
126
+ pendingToolCalls.set(event.toolCall.toolCallId, {
127
+ toolName: event.toolCall.toolName,
128
+ args: event.toolCall.args,
129
+ });
135
130
  }
131
+ read(event);
136
132
  });
137
133
  try {
138
- // Send the initial message
139
134
  await protocolAdapter.sendMessage(userMessage, {
140
135
  tools: requestOptions.tools,
141
136
  messages: allMessages,
142
137
  });
143
- // After stream completes, execute any pending tool calls
144
138
  if (pendingToolCalls.size > 0 && toolExecutor) {
145
- console.log('[useChat] Executing', pendingToolCalls.size, 'tool calls');
146
- // Execute tool calls and collect results
147
139
  const toolResults = [];
148
140
  for (const [toolCallId, { toolName, args }] of pendingToolCalls) {
149
- console.log('[useChat] Executing tool:', toolName, 'with args:', args);
150
141
  const result = await toolExecutor.execute({
151
142
  toolCallId,
152
143
  toolName,
153
144
  args,
154
145
  });
155
- console.log('[useChat] Tool result:', result);
156
146
  toolResults.push({
157
147
  toolCallId,
158
148
  toolName,
159
149
  result: result.result,
160
150
  isError: !result.success,
161
151
  });
152
+ // The call answered, in the conversation.
153
+ reduce(conversationEventsOf({
154
+ type: 'tool-result',
155
+ toolResult: {
156
+ toolCallId,
157
+ success: result.success,
158
+ result: result.result,
159
+ ...(result.success ? {} : { error: result.error }),
160
+ },
161
+ }, conversation));
162
162
  }
163
- // Build messages array with tool results for continuation
164
- // Include the assistant message with tool calls
163
+ // The run continued with the results: the assistant's calls,
164
+ // then one `tool` message per result (AG-UI).
165
165
  const assistantWithToolCalls = {
166
- id: assistantMessageId,
166
+ id: generateMessageId(),
167
167
  role: 'assistant',
168
- content: currentAssistantContent,
168
+ content: '',
169
169
  createdAt: new Date(),
170
170
  toolCalls: Array.from(pendingToolCalls.entries()).map(([toolCallId, { toolName, args }]) => ({
171
171
  type: 'tool-call',
@@ -175,7 +175,6 @@ export function useChat() {
175
175
  status: 'completed',
176
176
  })),
177
177
  };
178
- // Create tool result messages
179
178
  const toolResultMessages = toolResults.map(tr => ({
180
179
  id: generateMessageId(),
181
180
  role: 'tool',
@@ -187,48 +186,14 @@ export function useChat() {
187
186
  isError: tr.isError,
188
187
  },
189
188
  }));
190
- // Update stored messages
191
- updateMessage(assistantMessageId, assistantWithToolCalls);
192
- toolResultMessages.forEach(msg => addMessage(msg));
193
- // Create new assistant message for the continuation
194
- const continuationAssistantId = generateMessageId();
195
- const continuationMessage = createAssistantMessage('');
196
- continuationMessage.id = continuationAssistantId;
197
- addMessage(continuationMessage);
198
- startStreaming(continuationAssistantId);
199
- // Subscribe to continuation events
200
- const unsubscribeContinuation = protocolAdapter.subscribe(event => {
201
- if (event.type === 'message' && event.message) {
202
- const rawContent = typeof event.message.content === 'string'
203
- ? event.message.content
204
- : '';
205
- const content = sanitizeAssistantContent(rawContent);
206
- updateMessage(continuationAssistantId, {
207
- content,
208
- toolCalls: event.message.toolCalls,
209
- });
210
- }
211
- else if (event.type === 'error' && event.error) {
212
- setError(event.error);
213
- }
214
- });
215
- try {
216
- // Build the full message history for continuation
217
- const continuationMessages = [
189
+ await protocolAdapter.sendMessage(toolResultMessages[toolResultMessages.length - 1], {
190
+ tools: requestOptions.tools,
191
+ messages: [
218
192
  ...allMessages,
219
193
  assistantWithToolCalls,
220
194
  ...toolResultMessages,
221
- ];
222
- // Send continuation request with tool results
223
- // AG-UI expects tool results as messages with role: "tool"
224
- await protocolAdapter.sendMessage(toolResultMessages[toolResultMessages.length - 1], {
225
- tools: requestOptions.tools,
226
- messages: continuationMessages,
227
- });
228
- }
229
- finally {
230
- unsubscribeContinuation();
231
- }
195
+ ],
196
+ });
232
197
  }
233
198
  }
234
199
  finally {
@@ -236,47 +201,50 @@ export function useChat() {
236
201
  }
237
202
  }
238
203
  else if (inferenceProvider) {
204
+ const answerId = generateMessageId();
205
+ reduce([
206
+ { type: 'RUN_STARTED' },
207
+ {
208
+ type: 'TEXT_MESSAGE_START',
209
+ messageId: answerId,
210
+ role: 'assistant',
211
+ },
212
+ ]);
239
213
  const response = await inferenceProvider.streamMessage(allMessages, requestOptions, event => {
240
- switch (event.type) {
241
- case 'content-delta':
242
- if (event.delta?.delta.content) {
243
- appendToStream(assistantMessageId, event.delta.delta.content);
244
- }
245
- break;
246
- case 'tool-call-start':
247
- // Handle tool call UI update
248
- if (event.toolCall) {
249
- // Tool calls are handled by the tool executor
250
- console.log('[useChat] Tool call started:', event.toolCall);
251
- }
252
- break;
253
- case 'error':
254
- if (event.error) {
255
- setError(event.error);
256
- }
257
- break;
214
+ if (event.type === 'content-delta' &&
215
+ event.delta?.delta.content) {
216
+ reduce([
217
+ {
218
+ type: 'TEXT_MESSAGE_CONTENT',
219
+ messageId: answerId,
220
+ delta: event.delta.delta.content,
221
+ },
222
+ ]);
223
+ }
224
+ else if (event.type === 'error' && event.error) {
225
+ setError(event.error);
258
226
  }
259
227
  });
260
- // Update final message with complete content and tool calls
261
- const finalAssistantContent = typeof response.message.content === 'string'
262
- ? sanitizeAssistantContent(response.message.content)
263
- : response.message.content;
264
- updateMessage(assistantMessageId, {
265
- content: finalAssistantContent,
266
- toolCalls: response.message.toolCalls,
267
- });
268
- // Handle tool calls if present
269
- if (response.message.toolCalls &&
270
- response.message.toolCalls.length > 0) {
271
- // Tool calls are executed by the tool executor if configured
272
- if (toolExecutor) {
273
- for (const toolCall of response.message.toolCalls) {
274
- await toolExecutor.execute({
275
- toolCallId: toolCall.toolCallId,
276
- toolName: toolCall.toolName,
277
- args: toolCall.args,
278
- });
279
- }
228
+ // The answer as the provider gave it whole, then the run's end.
229
+ const said = conversationEventsOf({
230
+ type: 'message',
231
+ message: {
232
+ id: answerId,
233
+ role: 'assistant',
234
+ content: typeof response.message.content === 'string'
235
+ ? response.message.content
236
+ : '',
237
+ },
238
+ }, conversation);
239
+ reduce([...said, { type: 'RUN_FINISHED' }]);
240
+ // The page's tools run the calls it asked for.
241
+ if (toolExecutor && response.message.toolCalls?.length) {
242
+ for (const toolCall of response.message.toolCalls) {
243
+ await toolExecutor.execute({
244
+ toolCallId: toolCall.toolCallId,
245
+ toolName: toolCall.toolName,
246
+ args: toolCall.args,
247
+ });
280
248
  }
281
249
  }
282
250
  }
@@ -293,12 +261,10 @@ export function useChat() {
293
261
  protocolAdapter,
294
262
  toolExecutor,
295
263
  messages,
296
- addMessage,
297
- updateMessage,
264
+ setMessages,
298
265
  setLoading,
299
266
  setError,
300
267
  startStreaming,
301
- appendToStream,
302
268
  stopStreaming,
303
269
  getTools,
304
270
  ]);
@@ -0,0 +1,78 @@
1
+ export type RecordKind = 'session'
2
+ /** A run began: in progress until it answers (R-15). Not a step. */
3
+ | 'run' | 'tool_call' | 'decision' | 'check' | 'approval' | 'output' | 'feedback'
4
+ /** A turn of a conversation: what was asked, what it answered (V-16). */
5
+ | 'turn'
6
+ /** What it did, step by step (P-16); a harness's session, turn and plan events (SHIELD §6.4). */
7
+ | 'step'
8
+ /** What one of its channels was sent, or why not: `{channel, event, delivery, detail}` (R-37). */
9
+ | 'notification';
10
+ /** One entry of an application's record. */
11
+ export type RecordEntry = {
12
+ uid: string;
13
+ appUid: string;
14
+ sessionUid: string;
15
+ deploymentUid: string;
16
+ version: number;
17
+ kind: RecordKind;
18
+ summary: string;
19
+ payload: Record<string, unknown>;
20
+ createdAt: string;
21
+ /** `test` for a session its tests ran (the Evals engine's); empty for real use. */
22
+ purpose: string;
23
+ };
24
+ /** An entry as ai-agents answers with it. */
25
+ export declare function entryOf(raw: Record<string, unknown>): RecordEntry;
26
+ /** How the run an entry ends ended: `answered`, `stopped`, `gone`; `''` for any other entry. */
27
+ export declare function runEndOf(entry: RecordEntry): string;
28
+ /** An entry in words: its summary, or how its run ended when its words are not kept. */
29
+ export declare function entrySaid(entry: RecordEntry): string;
30
+ /** A session in a sentence: how many calls, what stopped it, how it ended. */
31
+ export declare function sessionSentence(entries: readonly RecordEntry[]): string;
32
+ /** A run its schedule plans, as the scheduler says (R-15). */
33
+ export type ScheduledRun = {
34
+ deploymentUid: string;
35
+ appUid: string;
36
+ version: number;
37
+ /** Which of its triggers, by position in its Appspec. */
38
+ position: number;
39
+ cron: string;
40
+ description: string;
41
+ /** When it runs next; empty when the scheduler does not say. */
42
+ nextAt: string;
43
+ };
44
+ /** What an application, or all of a person's, is doing (R-15): real use only, its tests read apart. */
45
+ export type AppActivity = {
46
+ /** Sessions whose latest run has not answered yet, newest first: their run's start. */
47
+ inProgress: RecordEntry[];
48
+ /** Its live deployments' next runs, soonest first. */
49
+ scheduled: ScheduledRun[];
50
+ /** Why its schedules could not be read; empty when they were. */
51
+ scheduledProblem: string;
52
+ /** Its other sessions, newest first: their opening entries. */
53
+ completed: RecordEntry[];
54
+ /** The offset of the next page of completed sessions; `null` after the last. */
55
+ nextOffset: number | null;
56
+ };
57
+ /** `/apps/activity`'s answer, read. */
58
+ export declare function activityOf(body: Record<string, unknown>): AppActivity;
59
+ /** Pages as one: in progress and scheduled from the first, completed from all, the last page's next offset. */
60
+ export declare function mergeActivityPages(pages: readonly AppActivity[]): AppActivity;
61
+ /** What Activity says, on every client (checked against the vocabulary). */
62
+ export declare const ACTIVITY_WORDS: {
63
+ readonly inProgress: "In progress";
64
+ readonly scheduled: "Scheduled";
65
+ readonly completed: "Completed";
66
+ readonly working: "Working since";
67
+ readonly next: "next";
68
+ readonly wakes: "It also starts on its own when something happens:";
69
+ readonly nothing: "Nothing yet.";
70
+ };
71
+ /** What happens that starts it on its own, in words (LOOP W-03): its event triggers. */
72
+ export declare const wakesOn: (triggers: ReadonlyArray<{
73
+ type: string;
74
+ event?: string;
75
+ description?: string;
76
+ }> | undefined) => string[];
77
+ /** A scheduled run in words: what it does, and on which version. */
78
+ export declare const scheduledSays: (run: ScheduledRun) => string;
@@ -0,0 +1,129 @@
1
+ /*
2
+ * Copyright (c) 2025-2026 Datalayer, Inc.
3
+ * Distributed under the terms of the Modified BSD License.
4
+ */
5
+ /**
6
+ * What an application is doing (LOOP R-15), as ai-agents answers
7
+ * `/api/ai-agents/v1/apps/activity`, and its record's entries
8
+ * (`/apps/sessions`, `/apps/records`): read and said the same by the Studio
9
+ * and Datalayer Mobile (MOBILE M-015b). Pure: each client fetches with its own
10
+ * authentication and hands the body here.
11
+ *
12
+ * @module portable/activity
13
+ */
14
+ import { RUN_END_WORDS } from './records';
15
+ const isRecord = (value) => Boolean(value) && typeof value === 'object' && !Array.isArray(value);
16
+ const records = (value) => Array.isArray(value) ? value.filter(isRecord) : [];
17
+ const said = (value) => typeof value === 'string' ? value : value == null ? '' : String(value);
18
+ /** An entry as ai-agents answers with it. */
19
+ export function entryOf(raw) {
20
+ return {
21
+ uid: said(raw.uid),
22
+ appUid: said(raw.app_uid),
23
+ sessionUid: said(raw.session_uid),
24
+ deploymentUid: said(raw.deployment_uid),
25
+ version: Number(raw.version) || 0,
26
+ kind: (said(raw.kind) || 'output'),
27
+ summary: said(raw.summary),
28
+ payload: isRecord(raw.payload) ? raw.payload : {},
29
+ createdAt: said(raw.created_at),
30
+ purpose: said(raw.purpose),
31
+ };
32
+ }
33
+ /** How the run an entry ends ended: `answered`, `stopped`, `gone`; `''` for any other entry. */
34
+ export function runEndOf(entry) {
35
+ const status = entry.kind === 'output' ? entry.payload.status : undefined;
36
+ return typeof status === 'string' && status in RUN_END_WORDS ? status : '';
37
+ }
38
+ /** An entry in words: its summary, or how its run ended when its words are not kept. */
39
+ export function entrySaid(entry) {
40
+ return entry.summary || RUN_END_WORDS[runEndOf(entry)] || '';
41
+ }
42
+ const plural = (n, word) => `${n} ${word}${n === 1 ? '' : 's'}`;
43
+ /** A session in a sentence: how many calls, what stopped it, how it ended. */
44
+ export function sessionSentence(entries) {
45
+ const calls = entries.filter(entry => entry.kind === 'tool_call').length;
46
+ const checks = entries.filter(entry => entry.kind === 'check');
47
+ const ends = entries.map(runEndOf).filter(Boolean);
48
+ const answers = ends.filter(end => end === 'answered').length;
49
+ const parts = [];
50
+ parts.push(calls === 0 ? 'No tool called' : plural(calls, 'tool call'));
51
+ if (checks.length) {
52
+ parts.push(`${plural(checks.length, 'step')} a check did not let pass`);
53
+ }
54
+ const unsent = entries.filter(entry => entry.kind === 'notification' && entry.payload.delivery !== 'sent');
55
+ if (unsent.length) {
56
+ parts.push(`${plural(unsent.length, 'notification')} not sent`);
57
+ }
58
+ if (ends[ends.length - 1] === 'stopped') {
59
+ parts.push('stopped');
60
+ }
61
+ else if (answers) {
62
+ parts.push(plural(answers, 'answer'));
63
+ }
64
+ return `${parts.join(', ')}.`;
65
+ }
66
+ /** `/apps/activity`'s answer, read. */
67
+ export function activityOf(body) {
68
+ return {
69
+ inProgress: records(body.in_progress).map(entryOf),
70
+ scheduled: records(body.scheduled).map(raw => ({
71
+ deploymentUid: said(raw.deployment_uid),
72
+ appUid: said(raw.app_uid),
73
+ version: Number(raw.version) || 0,
74
+ position: Number(raw.position) || 0,
75
+ cron: said(raw.cron),
76
+ description: said(raw.description),
77
+ nextAt: said(raw.next_at),
78
+ })),
79
+ scheduledProblem: said(body.scheduled_problem),
80
+ completed: records(body.completed).map(entryOf),
81
+ nextOffset: typeof body.next_offset === 'number' ? body.next_offset : null,
82
+ };
83
+ }
84
+ /** Pages as one: in progress and scheduled from the first, completed from all, the last page's next offset. */
85
+ export function mergeActivityPages(pages) {
86
+ const [first] = pages;
87
+ if (!first) {
88
+ return {
89
+ inProgress: [],
90
+ scheduled: [],
91
+ scheduledProblem: '',
92
+ completed: [],
93
+ nextOffset: null,
94
+ };
95
+ }
96
+ const seen = new Set();
97
+ const completed = pages
98
+ .flatMap(page => page.completed)
99
+ .filter(entry => {
100
+ const key = entry.uid || entry.sessionUid;
101
+ if (seen.has(key)) {
102
+ return false;
103
+ }
104
+ seen.add(key);
105
+ return true;
106
+ });
107
+ return {
108
+ ...first,
109
+ completed,
110
+ nextOffset: pages[pages.length - 1].nextOffset,
111
+ };
112
+ }
113
+ /** What Activity says, on every client (checked against the vocabulary). */
114
+ export const ACTIVITY_WORDS = {
115
+ inProgress: 'In progress',
116
+ scheduled: 'Scheduled',
117
+ completed: 'Completed',
118
+ working: 'Working since',
119
+ next: 'next',
120
+ wakes: 'It also starts on its own when something happens:',
121
+ nothing: 'Nothing yet.',
122
+ };
123
+ /** What happens that starts it on its own, in words (LOOP W-03): its event triggers. */
124
+ export const wakesOn = (triggers) => (triggers ?? [])
125
+ .filter(trigger => trigger.type === 'event' && (trigger.description || trigger.event))
126
+ .map(trigger => trigger.description ||
127
+ `When ${String(trigger.event).replace(/_/g, ' ')}`);
128
+ /** A scheduled run in words: what it does, and on which version. */
129
+ export const scheduledSays = (run) => `${run.description || 'Its schedule'} (${run.cron}) · version ${run.version}`;
@@ -18,3 +18,10 @@ export declare function shieldDisplayFixtures(): {
18
18
  terminal: Record<string, unknown>;
19
19
  };
20
20
  export declare function conversationFixture(): ConversationState;
21
+ /**
22
+ * `GET /api/ai-agents/v1/apps/activity`'s answer for one application: a
23
+ * session in progress, a scheduled run, a completed one ended by Stop, and a
24
+ * next page — what the Studio's Activity and the phone's read alike (MOBILE
25
+ * M-015b).
26
+ */
27
+ export declare function activityFixture(): Record<string, unknown>;
@@ -118,3 +118,53 @@ export function conversationFixture() {
118
118
  dropped: 3,
119
119
  };
120
120
  }
121
+ /**
122
+ * `GET /api/ai-agents/v1/apps/activity`'s answer for one application: a
123
+ * session in progress, a scheduled run, a completed one ended by Stop, and a
124
+ * next page — what the Studio's Activity and the phone's read alike (MOBILE
125
+ * M-015b).
126
+ */
127
+ export function activityFixture() {
128
+ return {
129
+ success: true,
130
+ in_progress: [
131
+ {
132
+ uid: 'e-run',
133
+ app_uid: 'app-1',
134
+ session_uid: 's-2',
135
+ deployment_uid: 'd-1',
136
+ version: 3,
137
+ kind: 'run',
138
+ summary: 'Triage the inbox',
139
+ payload: {},
140
+ created_at: '2026-10-16T08:00:00Z',
141
+ },
142
+ ],
143
+ scheduled: [
144
+ {
145
+ deployment_uid: 'd-1',
146
+ app_uid: 'app-1',
147
+ version: 3,
148
+ position: 0,
149
+ cron: '0 8 * * 1-5',
150
+ description: 'Every weekday morning',
151
+ next_at: '2026-10-17T08:00:00Z',
152
+ },
153
+ ],
154
+ scheduled_problem: '',
155
+ completed: [
156
+ {
157
+ uid: 'e-out',
158
+ app_uid: 'app-1',
159
+ session_uid: 's-1',
160
+ deployment_uid: 'd-1',
161
+ version: 3,
162
+ kind: 'output',
163
+ summary: '',
164
+ payload: { status: 'stopped' },
165
+ created_at: '2026-10-15T08:00:00Z',
166
+ },
167
+ ],
168
+ next_offset: 20,
169
+ };
170
+ }
@@ -16,7 +16,7 @@ export { BROWSER_LIMITS, CUT_MARK, DEVICE_LIMITS, DIAGNOSTIC_LIMIT, emptyConvers
16
16
  export { conversationEventsOf, sanitizeAssistantContent, snapshotOfMessages, type AdapterEvent, type SaidMessage, } from './adapterEvents';
17
17
  export { PRESENCE_LINES, presenceOfConversation, presenceState, presenceToolOf, type PresenceState, type PresenceTool, } from './presence';
18
18
  export { ANSWER_BELOW, ANSWER_COMPLETE, composerOf, droppedWords, ENDED_WORDS, messageLabel, OPENING_WORDS, presenceLine, showsQuestion, stepLine, TOOL_STATUS_WORDS, toolLabel, type ComposerProps, type ComposerState, type ConversationViewProps, type ErrorItem, type ErrorRowProps, type MessageItem, type MessageRowProps, type PresenceLineProps, type ProtocolDisplayProps, type QuestionCardProps, type StepItem, type StepRowProps, type ToolItem, type ToolRowProps, type TurnEndItem, type TurnEndRowProps, } from './views';
19
- export { conversationFixture } from './fixtures';
19
+ export { activityFixture, conversationFixture } from './fixtures';
20
20
  export { DISPLAY_META_KEY, displayLine, displayOfToolResult, PAP_CONFIRM_TYPE, PAP_HANDOFF_TOOL, PAP_OUTCOME_TYPE, PAP_REPLIES_TYPE, PAP_SIGN_IN_TYPE, protocolDisplayOf, replyAuthor, type ActionClass, type ProtocolDisplay, DIFF_LINES_LIMIT, SHIELD_DIFF_TYPE, SHIELD_TERMINAL_TYPE, languageOf, unifiedDiffLines, unifiedDiffText, type DiffLine, } from './displays';
21
21
  export { shieldDisplayFixtures } from './fixtures';
22
22
  export { isPapSignInTool, PAP_SIGN_IN_WORDS, readPapSignIn, safeSignInUrl, type PapSignInWaiting, } from './signIn';
@@ -24,5 +24,8 @@ export { DIRECT_MAX, DIRECT_WORDS, directAnswerOf, directAuthor, directClient, o
24
24
  export { SIGN_IN_POLL_MS, SIGN_IN_STATUS_PATH, signInStandsOf, signInStatusClient, type SignInStands, } from './signInStatus';
25
25
  export { emptyFields, fieldProblems, MAX_VALUE_LENGTH, PROTECTED_INPUT_WORDS, protectedInputClient, protectedRequestOf, ProtectedRequestGone, sentWords, submitBody, submitWords, ticketComplete, ticketOfPage, type ProtectedField, type ProtectedRequest, } from './protectedInput';
26
26
  export { failureOf, otlpMetricsOf, RunMeter, type RunMeasure, type StreamFailure, } from './runMeasures';
27
+ export { ACTIVITY_WORDS, activityOf, entryOf, entrySaid, mergeActivityPages, runEndOf, scheduledSays, sessionSentence, wakesOn, type AppActivity, type RecordEntry, type RecordKind, type ScheduledRun, } from './activity';
28
+ export { deploymentEventLine, deploymentEventOf, notDeletedOf, resetBody, resetCountsOf, resetLines, stateChangeBody, stoppedWorkLine, stoppedWorkOf, type DeploymentEvent, type DeploymentEventKind, type DeploymentState, type ResetCounts, type StoppedWork, } from './lifecycle';
29
+ export { agentRuntimesOf, agentSummaryOfRuntime, CREATING_STATUSES, isAgentRuntime, isCreatingAgent, runtimeOf, type AgentRuntime, type AgentSummaryData, } from './runtimes';
27
30
  export { agentRunClient, agentRuntimesUrlOf, appSessionsClient, RuntimeRefusal, type AgentRunClient, type AgentTurn, type AppSessionsClient, type RuntimeClientOptions, type SessionStart, type StreamOptions, } from './sessions';
28
31
  export type { ChatMessage } from '../types/messages';