@roj-ai/shared 0.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/chat-protocol.d.ts +60 -0
- package/dist/chat-protocol.d.ts.map +1 -0
- package/dist/index.d.ts +13 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/lib/domain-utils.d.ts +16 -0
- package/dist/lib/domain-utils.d.ts.map +1 -0
- package/dist/lib/ids.d.ts +18 -0
- package/dist/lib/ids.d.ts.map +1 -0
- package/dist/lib/result.d.ts +26 -0
- package/dist/lib/result.d.ts.map +1 -0
- package/dist/projections/agent-detail-projection.d.ts +91 -0
- package/dist/projections/agent-detail-projection.d.ts.map +1 -0
- package/dist/projections/agent-registry.d.ts +16 -0
- package/dist/projections/agent-registry.d.ts.map +1 -0
- package/dist/projections/agent-tree-projection.d.ts +30 -0
- package/dist/projections/agent-tree-projection.d.ts.map +1 -0
- package/dist/projections/chat-debug.d.ts +34 -0
- package/dist/projections/chat-debug.d.ts.map +1 -0
- package/dist/projections/events.d.ts +9 -0
- package/dist/projections/events.d.ts.map +1 -0
- package/dist/projections/index.d.ts +22 -0
- package/dist/projections/index.d.ts.map +1 -0
- package/dist/projections/mailbox.d.ts +21 -0
- package/dist/projections/mailbox.d.ts.map +1 -0
- package/dist/projections/metrics.d.ts +30 -0
- package/dist/projections/metrics.d.ts.map +1 -0
- package/dist/projections/protocol-status.d.ts +9 -0
- package/dist/projections/protocol-status.d.ts.map +1 -0
- package/dist/projections/services-projection.d.ts +24 -0
- package/dist/projections/services-projection.d.ts.map +1 -0
- package/dist/projections/session-info.d.ts +19 -0
- package/dist/projections/session-info.d.ts.map +1 -0
- package/dist/projections/timeline.d.ts +26 -0
- package/dist/projections/timeline.d.ts.map +1 -0
- package/dist/projections/types.d.ts +182 -0
- package/dist/projections/types.d.ts.map +1 -0
- package/dist/rpc/client.d.ts +79 -0
- package/dist/rpc/client.d.ts.map +1 -0
- package/dist/rpc/index.d.ts +9 -0
- package/dist/rpc/index.d.ts.map +1 -0
- package/dist/src/api-types.d.ts +8 -0
- package/dist/src/index.d.ts +17 -0
- package/dist/src/rpc/admin-methods.d.ts +99 -0
- package/dist/src/rpc/client.d.ts +26 -0
- package/dist/src/rpc/definition.d.ts +39 -0
- package/dist/src/rpc/instance-methods.d.ts +94 -0
- package/dist/src/rpc/methods.d.ts +260 -0
- package/dist/src/rpc/server.d.ts +21 -0
- package/dist/src/workspace-config.d.ts +16 -0
- package/dist/tsconfig.tsbuildinfo +1 -0
- package/package.json +36 -0
- package/src/chat-protocol.ts +46 -0
- package/src/globals.d.ts +3 -0
- package/src/index.ts +82 -0
- package/src/lib/domain-utils.ts +26 -0
- package/src/lib/ids.ts +19 -0
- package/src/lib/result.ts +35 -0
- package/src/projections/agent-detail-projection.ts +623 -0
- package/src/projections/agent-registry.ts +37 -0
- package/src/projections/agent-tree-projection.ts +229 -0
- package/src/projections/chat-debug.ts +260 -0
- package/src/projections/events.ts +10 -0
- package/src/projections/index.ts +59 -0
- package/src/projections/mailbox.ts +113 -0
- package/src/projections/metrics.ts +111 -0
- package/src/projections/protocol-status.ts +23 -0
- package/src/projections/services-projection.ts +89 -0
- package/src/projections/session-info.ts +47 -0
- package/src/projections/timeline.ts +228 -0
- package/src/projections/types.ts +237 -0
- package/src/rpc/client.ts +188 -0
- package/src/rpc/index.ts +14 -0
|
@@ -0,0 +1,623 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Agent detail projection - self-contained projection for agent detail views.
|
|
3
|
+
*
|
|
4
|
+
* Replaces useAgentDetail (client) and buildAgentDetail (CLI) that needed full SessionState.
|
|
5
|
+
* Tracks per-agent: conversation history, tool calls, mailbox, counters, skills.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import type { AgentCounters, AgentId, AgentPauseReason, LLMCallId, MessageId, ToolCallId } from '@roj-ai/sdk'
|
|
9
|
+
import { contentToString } from '../lib/domain-utils.js'
|
|
10
|
+
import type { ProjectionEvent } from './events.js'
|
|
11
|
+
import { toProtocolStatus } from './protocol-status.js'
|
|
12
|
+
import type { ConversationMessageView, GetAgentDetailResponse, MailboxMessageView, ToolCallView } from './types.js'
|
|
13
|
+
|
|
14
|
+
// ============================================================================
|
|
15
|
+
// Agent view model (internal per-agent state)
|
|
16
|
+
// ============================================================================
|
|
17
|
+
|
|
18
|
+
interface LLMMessage {
|
|
19
|
+
role: 'user' | 'assistant' | 'tool' | 'system'
|
|
20
|
+
content: string | Array<{ type: string; text?: string }>
|
|
21
|
+
toolCalls?: Array<{ id: ToolCallId; name: string; input: unknown }>
|
|
22
|
+
toolCallId?: ToolCallId
|
|
23
|
+
isError?: boolean
|
|
24
|
+
sourceMessageIds?: MessageId[]
|
|
25
|
+
timestamp?: number
|
|
26
|
+
cost?: number
|
|
27
|
+
llmCallId?: LLMCallId
|
|
28
|
+
promptTokens?: number
|
|
29
|
+
cachedTokens?: number
|
|
30
|
+
cacheWriteTokens?: number
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
interface ToolCall {
|
|
34
|
+
id: ToolCallId
|
|
35
|
+
name: string
|
|
36
|
+
input: unknown
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
interface PendingToolResult {
|
|
40
|
+
toolCallId: ToolCallId
|
|
41
|
+
toolName: string
|
|
42
|
+
timestamp: number
|
|
43
|
+
isError: boolean
|
|
44
|
+
content: string | Array<{ type: string; text?: string }>
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
interface AgentViewModel {
|
|
48
|
+
id: AgentId
|
|
49
|
+
definitionName: string
|
|
50
|
+
status: 'pending' | 'inferring' | 'tool_exec' | 'errored' | 'paused'
|
|
51
|
+
parentId: AgentId | null
|
|
52
|
+
conversationHistory: LLMMessage[]
|
|
53
|
+
pendingToolCalls: ToolCall[]
|
|
54
|
+
executingToolCall?: { toolCallId: ToolCallId; toolName: string; startedAt: number }
|
|
55
|
+
pendingToolResults: PendingToolResult[]
|
|
56
|
+
/** Messages pending addition to history (set by inference_started, committed by inference_completed) */
|
|
57
|
+
pendingMessages: LLMMessage[]
|
|
58
|
+
typedInput?: unknown
|
|
59
|
+
pauseReason?: AgentPauseReason
|
|
60
|
+
pauseMessage?: string
|
|
61
|
+
cost: number
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
interface MailboxEntry {
|
|
65
|
+
id: MessageId
|
|
66
|
+
from: string
|
|
67
|
+
content: string
|
|
68
|
+
timestamp: number
|
|
69
|
+
consumed: boolean
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
interface LoadedSkillEntry {
|
|
73
|
+
id: string
|
|
74
|
+
name: string
|
|
75
|
+
loadedAt: number
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// ============================================================================
|
|
79
|
+
// State
|
|
80
|
+
// ============================================================================
|
|
81
|
+
|
|
82
|
+
export interface AgentDetailProjectionState {
|
|
83
|
+
agents: Map<AgentId, AgentViewModel>
|
|
84
|
+
agentMailboxes: Map<AgentId, MailboxEntry[]>
|
|
85
|
+
agentCounters: Map<AgentId, AgentCounters>
|
|
86
|
+
agentSkills: Map<AgentId, LoadedSkillEntry[]>
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
export function createAgentDetailProjectionState(): AgentDetailProjectionState {
|
|
90
|
+
return {
|
|
91
|
+
agents: new Map(),
|
|
92
|
+
agentMailboxes: new Map(),
|
|
93
|
+
agentCounters: new Map(),
|
|
94
|
+
agentSkills: new Map(),
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
const createDefaultCounters = (): AgentCounters => ({
|
|
99
|
+
inferenceCount: 0,
|
|
100
|
+
toolCallCount: 0,
|
|
101
|
+
spawnedAgentCount: 0,
|
|
102
|
+
messagesSentCount: 0,
|
|
103
|
+
consecutiveToolFailures: {},
|
|
104
|
+
recentToolCallHashes: [],
|
|
105
|
+
recentResponseHashes: [],
|
|
106
|
+
})
|
|
107
|
+
|
|
108
|
+
// ============================================================================
|
|
109
|
+
// Reducer
|
|
110
|
+
// ============================================================================
|
|
111
|
+
|
|
112
|
+
export function applyEventToAgentDetail(state: AgentDetailProjectionState, event: ProjectionEvent): AgentDetailProjectionState {
|
|
113
|
+
switch (event.type) {
|
|
114
|
+
// ---- Core agent lifecycle ----
|
|
115
|
+
|
|
116
|
+
case 'agent_spawned': {
|
|
117
|
+
const newAgents = new Map(state.agents)
|
|
118
|
+
newAgents.set(event.agentId, {
|
|
119
|
+
id: event.agentId,
|
|
120
|
+
definitionName: event.definitionName,
|
|
121
|
+
parentId: event.parentId,
|
|
122
|
+
status: 'pending',
|
|
123
|
+
conversationHistory: [],
|
|
124
|
+
pendingToolCalls: [],
|
|
125
|
+
pendingToolResults: [],
|
|
126
|
+
pendingMessages: [],
|
|
127
|
+
typedInput: event.typedInput,
|
|
128
|
+
cost: 0,
|
|
129
|
+
})
|
|
130
|
+
|
|
131
|
+
const newCounters = new Map(state.agentCounters)
|
|
132
|
+
newCounters.set(event.agentId, createDefaultCounters())
|
|
133
|
+
|
|
134
|
+
// Increment parent's spawnedAgentCount
|
|
135
|
+
if (event.parentId) {
|
|
136
|
+
const parentCounters = newCounters.get(event.parentId)
|
|
137
|
+
if (parentCounters) {
|
|
138
|
+
newCounters.set(event.parentId, {
|
|
139
|
+
...parentCounters,
|
|
140
|
+
spawnedAgentCount: parentCounters.spawnedAgentCount + 1,
|
|
141
|
+
})
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
return { ...state, agents: newAgents, agentCounters: newCounters }
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
case 'agent_state_changed': {
|
|
149
|
+
const agent = state.agents.get(event.agentId)
|
|
150
|
+
if (!agent) return state
|
|
151
|
+
const newAgents = new Map(state.agents)
|
|
152
|
+
newAgents.set(event.agentId, { ...agent, status: event.toState })
|
|
153
|
+
return { ...state, agents: newAgents }
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
case 'inference_started': {
|
|
157
|
+
const agent = state.agents.get(event.agentId)
|
|
158
|
+
if (!agent) return state
|
|
159
|
+
const newAgents = new Map(state.agents)
|
|
160
|
+
newAgents.set(event.agentId, {
|
|
161
|
+
...agent,
|
|
162
|
+
status: 'inferring',
|
|
163
|
+
pendingMessages: event.messages.map((m) => ({ ...m, timestamp: event.timestamp })),
|
|
164
|
+
})
|
|
165
|
+
return { ...state, agents: newAgents }
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
case 'inference_completed': {
|
|
169
|
+
const agent = state.agents.get(event.agentId)
|
|
170
|
+
if (!agent) return state
|
|
171
|
+
|
|
172
|
+
const toolCalls: ToolCall[] = event.response.toolCalls.map((tc) => ({
|
|
173
|
+
id: tc.id,
|
|
174
|
+
name: tc.name,
|
|
175
|
+
input: tc.input,
|
|
176
|
+
}))
|
|
177
|
+
|
|
178
|
+
const assistantMessage: LLMMessage = {
|
|
179
|
+
role: 'assistant',
|
|
180
|
+
content: event.response.content ?? '',
|
|
181
|
+
toolCalls: toolCalls.length > 0 ? toolCalls : undefined,
|
|
182
|
+
timestamp: event.timestamp,
|
|
183
|
+
cost: event.metrics.cost ?? undefined,
|
|
184
|
+
llmCallId: event.llmCallId ?? undefined,
|
|
185
|
+
promptTokens: event.metrics.promptTokens,
|
|
186
|
+
cachedTokens: event.metrics.cachedTokens ?? undefined,
|
|
187
|
+
cacheWriteTokens: event.metrics.cacheWriteTokens ?? undefined,
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
const hasToolCalls = toolCalls.length > 0
|
|
191
|
+
|
|
192
|
+
const newAgents = new Map(state.agents)
|
|
193
|
+
newAgents.set(event.agentId, {
|
|
194
|
+
...agent,
|
|
195
|
+
status: hasToolCalls ? 'tool_exec' : 'pending',
|
|
196
|
+
conversationHistory: [...agent.conversationHistory, ...agent.pendingMessages, assistantMessage],
|
|
197
|
+
pendingToolCalls: toolCalls,
|
|
198
|
+
pendingMessages: [],
|
|
199
|
+
pendingToolResults: [],
|
|
200
|
+
cost: agent.cost + (event.metrics.cost ?? 0),
|
|
201
|
+
})
|
|
202
|
+
|
|
203
|
+
// Update counters
|
|
204
|
+
const counters = state.agentCounters.get(event.agentId)
|
|
205
|
+
if (counters) {
|
|
206
|
+
const newCounters = new Map(state.agentCounters)
|
|
207
|
+
newCounters.set(event.agentId, {
|
|
208
|
+
...counters,
|
|
209
|
+
inferenceCount: counters.inferenceCount + 1,
|
|
210
|
+
})
|
|
211
|
+
return { ...state, agents: newAgents, agentCounters: newCounters }
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
return { ...state, agents: newAgents }
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
case 'inference_failed': {
|
|
218
|
+
const agent = state.agents.get(event.agentId)
|
|
219
|
+
if (!agent) return state
|
|
220
|
+
const newAgents = new Map(state.agents)
|
|
221
|
+
newAgents.set(event.agentId, {
|
|
222
|
+
...agent,
|
|
223
|
+
status: 'errored',
|
|
224
|
+
pendingMessages: [],
|
|
225
|
+
})
|
|
226
|
+
return { ...state, agents: newAgents }
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
case 'tool_started': {
|
|
230
|
+
const agent = state.agents.get(event.agentId)
|
|
231
|
+
if (!agent) return state
|
|
232
|
+
const newAgents = new Map(state.agents)
|
|
233
|
+
newAgents.set(event.agentId, {
|
|
234
|
+
...agent,
|
|
235
|
+
executingToolCall: {
|
|
236
|
+
toolCallId: event.toolCallId,
|
|
237
|
+
toolName: event.toolName,
|
|
238
|
+
startedAt: event.timestamp,
|
|
239
|
+
},
|
|
240
|
+
})
|
|
241
|
+
|
|
242
|
+
// Update counters
|
|
243
|
+
const counters = state.agentCounters.get(event.agentId)
|
|
244
|
+
if (counters) {
|
|
245
|
+
const newCounters = new Map(state.agentCounters)
|
|
246
|
+
newCounters.set(event.agentId, {
|
|
247
|
+
...counters,
|
|
248
|
+
toolCallCount: counters.toolCallCount + 1,
|
|
249
|
+
})
|
|
250
|
+
return { ...state, agents: newAgents, agentCounters: newCounters }
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
return { ...state, agents: newAgents }
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
case 'tool_completed': {
|
|
257
|
+
const agent = state.agents.get(event.agentId)
|
|
258
|
+
if (!agent) return state
|
|
259
|
+
const remaining = agent.pendingToolCalls.filter((tc) => tc.id !== event.toolCallId)
|
|
260
|
+
const toolName = agent.executingToolCall?.toolName
|
|
261
|
+
?? agent.pendingToolCalls.find((tc) => tc.id === event.toolCallId)?.name
|
|
262
|
+
?? 'unknown'
|
|
263
|
+
|
|
264
|
+
const pendingToolResult: PendingToolResult = {
|
|
265
|
+
toolCallId: event.toolCallId,
|
|
266
|
+
toolName,
|
|
267
|
+
timestamp: event.timestamp,
|
|
268
|
+
isError: false,
|
|
269
|
+
content: event.result,
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
const newAgents = new Map(state.agents)
|
|
273
|
+
newAgents.set(event.agentId, {
|
|
274
|
+
...agent,
|
|
275
|
+
pendingToolCalls: remaining,
|
|
276
|
+
pendingToolResults: [...agent.pendingToolResults, pendingToolResult],
|
|
277
|
+
status: remaining.length === 0 ? 'pending' : 'tool_exec',
|
|
278
|
+
executingToolCall: undefined,
|
|
279
|
+
})
|
|
280
|
+
|
|
281
|
+
// Reset consecutive failures for this tool
|
|
282
|
+
const counters = state.agentCounters.get(event.agentId)
|
|
283
|
+
if (counters) {
|
|
284
|
+
const { [toolName]: _, ...restFailures } = counters.consecutiveToolFailures
|
|
285
|
+
const newCounters = new Map(state.agentCounters)
|
|
286
|
+
newCounters.set(event.agentId, {
|
|
287
|
+
...counters,
|
|
288
|
+
consecutiveToolFailures: restFailures,
|
|
289
|
+
})
|
|
290
|
+
return { ...state, agents: newAgents, agentCounters: newCounters }
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
return { ...state, agents: newAgents }
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
case 'tool_failed': {
|
|
297
|
+
const agent = state.agents.get(event.agentId)
|
|
298
|
+
if (!agent) return state
|
|
299
|
+
const remaining = agent.pendingToolCalls.filter((tc) => tc.id !== event.toolCallId)
|
|
300
|
+
const toolName = agent.executingToolCall?.toolName
|
|
301
|
+
?? agent.pendingToolCalls.find((tc) => tc.id === event.toolCallId)?.name
|
|
302
|
+
?? 'unknown'
|
|
303
|
+
|
|
304
|
+
const pendingToolResult: PendingToolResult = {
|
|
305
|
+
toolCallId: event.toolCallId,
|
|
306
|
+
toolName,
|
|
307
|
+
timestamp: event.timestamp,
|
|
308
|
+
isError: true,
|
|
309
|
+
content: event.error,
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
const newAgents = new Map(state.agents)
|
|
313
|
+
newAgents.set(event.agentId, {
|
|
314
|
+
...agent,
|
|
315
|
+
pendingToolCalls: remaining,
|
|
316
|
+
pendingToolResults: [...agent.pendingToolResults, pendingToolResult],
|
|
317
|
+
status: remaining.length === 0 ? 'pending' : 'tool_exec',
|
|
318
|
+
executingToolCall: undefined,
|
|
319
|
+
})
|
|
320
|
+
|
|
321
|
+
// Increment consecutive failures for this tool
|
|
322
|
+
const counters = state.agentCounters.get(event.agentId)
|
|
323
|
+
if (counters) {
|
|
324
|
+
const currentEntry = counters.consecutiveToolFailures[toolName]
|
|
325
|
+
const newCounters = new Map(state.agentCounters)
|
|
326
|
+
newCounters.set(event.agentId, {
|
|
327
|
+
...counters,
|
|
328
|
+
consecutiveToolFailures: {
|
|
329
|
+
...counters.consecutiveToolFailures,
|
|
330
|
+
[toolName]: { count: (currentEntry?.count ?? 0) + 1, lastError: event.error },
|
|
331
|
+
},
|
|
332
|
+
})
|
|
333
|
+
return { ...state, agents: newAgents, agentCounters: newCounters }
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
return { ...state, agents: newAgents }
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
case 'context_compacted': {
|
|
340
|
+
const agent = state.agents.get(event.agentId)
|
|
341
|
+
if (!agent) return state
|
|
342
|
+
const newAgents = new Map(state.agents)
|
|
343
|
+
newAgents.set(event.agentId, {
|
|
344
|
+
...agent,
|
|
345
|
+
conversationHistory: event.newConversationHistory.map((m): LLMMessage => ({
|
|
346
|
+
role: m.role,
|
|
347
|
+
content: m.content,
|
|
348
|
+
})),
|
|
349
|
+
})
|
|
350
|
+
return { ...state, agents: newAgents }
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
case 'agent_paused': {
|
|
354
|
+
const agent = state.agents.get(event.agentId)
|
|
355
|
+
if (!agent) return state
|
|
356
|
+
const newAgents = new Map(state.agents)
|
|
357
|
+
newAgents.set(event.agentId, {
|
|
358
|
+
...agent,
|
|
359
|
+
status: 'paused',
|
|
360
|
+
pauseReason: event.reason,
|
|
361
|
+
pauseMessage: event.message,
|
|
362
|
+
})
|
|
363
|
+
return { ...state, agents: newAgents }
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
case 'agent_resumed': {
|
|
367
|
+
const agent = state.agents.get(event.agentId)
|
|
368
|
+
if (!agent) return state
|
|
369
|
+
const newAgents = new Map(state.agents)
|
|
370
|
+
newAgents.set(event.agentId, {
|
|
371
|
+
...agent,
|
|
372
|
+
status: 'pending',
|
|
373
|
+
pauseReason: undefined,
|
|
374
|
+
pauseMessage: undefined,
|
|
375
|
+
})
|
|
376
|
+
|
|
377
|
+
// Reset counters on resume
|
|
378
|
+
const counters = state.agentCounters.get(event.agentId)
|
|
379
|
+
if (counters) {
|
|
380
|
+
const newCounters = new Map(state.agentCounters)
|
|
381
|
+
newCounters.set(event.agentId, {
|
|
382
|
+
...counters,
|
|
383
|
+
inferenceCount: 0,
|
|
384
|
+
toolCallCount: 0,
|
|
385
|
+
spawnedAgentCount: 0,
|
|
386
|
+
messagesSentCount: 0,
|
|
387
|
+
consecutiveToolFailures: {},
|
|
388
|
+
recentToolCallHashes: [],
|
|
389
|
+
recentResponseHashes: [],
|
|
390
|
+
})
|
|
391
|
+
return { ...state, agents: newAgents, agentCounters: newCounters }
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
return { ...state, agents: newAgents }
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
case 'session_restarted': {
|
|
398
|
+
const newAgents = new Map(state.agents)
|
|
399
|
+
for (const [agentId, agent] of state.agents) {
|
|
400
|
+
let updated = agent
|
|
401
|
+
let changed = false
|
|
402
|
+
|
|
403
|
+
if (agent.status === 'inferring') {
|
|
404
|
+
updated = { ...updated, status: 'pending' as const, pendingMessages: [] }
|
|
405
|
+
changed = true
|
|
406
|
+
}
|
|
407
|
+
if (agent.executingToolCall !== undefined) {
|
|
408
|
+
updated = { ...updated, executingToolCall: undefined }
|
|
409
|
+
changed = true
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
if (changed) {
|
|
413
|
+
newAgents.set(agentId, updated)
|
|
414
|
+
}
|
|
415
|
+
}
|
|
416
|
+
return { ...state, agents: newAgents }
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
case 'agent_conversation_spliced': {
|
|
420
|
+
const agent = state.agents.get(event.agentId)
|
|
421
|
+
if (!agent) return state
|
|
422
|
+
const newHistory = [...agent.conversationHistory]
|
|
423
|
+
newHistory.splice(event.start, event.deleteCount, ...(event.insert ?? []))
|
|
424
|
+
const newAgents = new Map(state.agents)
|
|
425
|
+
newAgents.set(event.agentId, {
|
|
426
|
+
...agent,
|
|
427
|
+
conversationHistory: newHistory,
|
|
428
|
+
pendingToolCalls: [],
|
|
429
|
+
pendingToolResults: [],
|
|
430
|
+
pendingMessages: [],
|
|
431
|
+
executingToolCall: undefined,
|
|
432
|
+
status: 'pending',
|
|
433
|
+
})
|
|
434
|
+
return { ...state, agents: newAgents }
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
// ---- Mailbox ----
|
|
438
|
+
|
|
439
|
+
case 'mailbox_message': {
|
|
440
|
+
const msg = event.message
|
|
441
|
+
const entry: MailboxEntry = {
|
|
442
|
+
id: msg.id,
|
|
443
|
+
from: msg.from,
|
|
444
|
+
content: msg.content,
|
|
445
|
+
timestamp: msg.timestamp,
|
|
446
|
+
consumed: msg.consumed,
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
const existing = state.agentMailboxes.get(event.toAgentId) ?? []
|
|
450
|
+
const newMailboxes = new Map(state.agentMailboxes)
|
|
451
|
+
newMailboxes.set(event.toAgentId, [...existing, entry])
|
|
452
|
+
|
|
453
|
+
// Increment sender's messagesSentCount if sender is an agent
|
|
454
|
+
const senderAgentId = typeof msg.from === 'string' && msg.from !== 'user'
|
|
455
|
+
&& msg.from !== 'orchestrator' && msg.from !== 'communicator'
|
|
456
|
+
? msg.from
|
|
457
|
+
: null
|
|
458
|
+
|
|
459
|
+
if (senderAgentId) {
|
|
460
|
+
const senderCounters = state.agentCounters.get(senderAgentId as AgentId)
|
|
461
|
+
if (senderCounters) {
|
|
462
|
+
const newCounters = new Map(state.agentCounters)
|
|
463
|
+
newCounters.set(senderAgentId as AgentId, {
|
|
464
|
+
...senderCounters,
|
|
465
|
+
messagesSentCount: senderCounters.messagesSentCount + 1,
|
|
466
|
+
})
|
|
467
|
+
return { ...state, agentMailboxes: newMailboxes, agentCounters: newCounters }
|
|
468
|
+
}
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
return { ...state, agentMailboxes: newMailboxes }
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
case 'mailbox_consumed': {
|
|
475
|
+
const existing = state.agentMailboxes.get(event.agentId)
|
|
476
|
+
if (!existing) return state
|
|
477
|
+
|
|
478
|
+
const consumedSet = new Set(event.messageIds as string[])
|
|
479
|
+
const updated = existing.map((m) => consumedSet.has(m.id) ? { ...m, consumed: true } : m)
|
|
480
|
+
const newMailboxes = new Map(state.agentMailboxes)
|
|
481
|
+
newMailboxes.set(event.agentId, updated)
|
|
482
|
+
return { ...state, agentMailboxes: newMailboxes }
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
// ---- Skills ----
|
|
486
|
+
|
|
487
|
+
case 'skill_loaded': {
|
|
488
|
+
const agentSkills = state.agentSkills.get(event.agentId) ?? []
|
|
489
|
+
const newSkills = new Map(state.agentSkills)
|
|
490
|
+
newSkills.set(event.agentId, [...agentSkills, {
|
|
491
|
+
id: event.skillId,
|
|
492
|
+
name: event.skillName,
|
|
493
|
+
loadedAt: event.timestamp,
|
|
494
|
+
}])
|
|
495
|
+
return { ...state, agentSkills: newSkills }
|
|
496
|
+
}
|
|
497
|
+
|
|
498
|
+
default:
|
|
499
|
+
return state
|
|
500
|
+
}
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
// ============================================================================
|
|
504
|
+
// Query
|
|
505
|
+
// ============================================================================
|
|
506
|
+
|
|
507
|
+
const truncate = (text: string): string => text.length > 500 ? text.slice(0, 500) + '...' : text
|
|
508
|
+
|
|
509
|
+
/**
|
|
510
|
+
* Get agent detail response from projection state.
|
|
511
|
+
*/
|
|
512
|
+
export function getAgentDetail(state: AgentDetailProjectionState, agentId: AgentId): GetAgentDetailResponse | null {
|
|
513
|
+
const agent = state.agents.get(agentId)
|
|
514
|
+
if (!agent) return null
|
|
515
|
+
|
|
516
|
+
// Mailbox
|
|
517
|
+
const agentMailbox = state.agentMailboxes.get(agentId) ?? []
|
|
518
|
+
const mailbox: MailboxMessageView[] = agentMailbox.map((m) => ({
|
|
519
|
+
id: m.id,
|
|
520
|
+
from: m.from,
|
|
521
|
+
content: m.content,
|
|
522
|
+
timestamp: m.timestamp,
|
|
523
|
+
consumed: m.consumed,
|
|
524
|
+
}))
|
|
525
|
+
|
|
526
|
+
// Conversation history
|
|
527
|
+
const conversationHistory: ConversationMessageView[] = agent.conversationHistory.map((m) => {
|
|
528
|
+
switch (m.role) {
|
|
529
|
+
case 'user': {
|
|
530
|
+
const full = contentToString(m.content)
|
|
531
|
+
return { role: 'user' as const, content: truncate(full), fullContent: full, timestamp: m.timestamp }
|
|
532
|
+
}
|
|
533
|
+
case 'assistant': {
|
|
534
|
+
const full = contentToString(m.content)
|
|
535
|
+
return {
|
|
536
|
+
role: 'assistant' as const,
|
|
537
|
+
content: truncate(full),
|
|
538
|
+
fullContent: full,
|
|
539
|
+
toolCalls: m.toolCalls?.map((tc) => ({
|
|
540
|
+
id: tc.id,
|
|
541
|
+
name: tc.name,
|
|
542
|
+
input: tc.input,
|
|
543
|
+
})),
|
|
544
|
+
timestamp: m.timestamp,
|
|
545
|
+
cost: m.cost,
|
|
546
|
+
llmCallId: m.llmCallId,
|
|
547
|
+
promptTokens: m.promptTokens,
|
|
548
|
+
cachedTokens: m.cachedTokens,
|
|
549
|
+
cacheWriteTokens: m.cacheWriteTokens,
|
|
550
|
+
}
|
|
551
|
+
}
|
|
552
|
+
case 'tool': {
|
|
553
|
+
const full = contentToString(m.content)
|
|
554
|
+
return {
|
|
555
|
+
role: 'tool' as const,
|
|
556
|
+
toolCallId: m.toolCallId!,
|
|
557
|
+
content: truncate(full),
|
|
558
|
+
fullContent: full,
|
|
559
|
+
isError: m.isError ?? false,
|
|
560
|
+
timestamp: m.timestamp,
|
|
561
|
+
}
|
|
562
|
+
}
|
|
563
|
+
case 'system': {
|
|
564
|
+
const full = contentToString(m.content)
|
|
565
|
+
return { role: 'system' as const, content: truncate(full), fullContent: full, timestamp: m.timestamp }
|
|
566
|
+
}
|
|
567
|
+
}
|
|
568
|
+
})
|
|
569
|
+
|
|
570
|
+
// Pending tool calls
|
|
571
|
+
const pendingToolCalls: ToolCallView[] = [
|
|
572
|
+
...agent.pendingToolCalls.map(
|
|
573
|
+
(tc): ToolCallView => ({
|
|
574
|
+
id: tc.id,
|
|
575
|
+
name: tc.name,
|
|
576
|
+
input: tc.input,
|
|
577
|
+
status: 'pending' as const,
|
|
578
|
+
}),
|
|
579
|
+
),
|
|
580
|
+
...(agent.executingToolCall
|
|
581
|
+
? [
|
|
582
|
+
{
|
|
583
|
+
id: agent.executingToolCall.toolCallId,
|
|
584
|
+
name: agent.executingToolCall.toolName,
|
|
585
|
+
input: undefined,
|
|
586
|
+
status: 'executing' as const,
|
|
587
|
+
} satisfies ToolCallView,
|
|
588
|
+
]
|
|
589
|
+
: []),
|
|
590
|
+
...agent.pendingToolResults.map(
|
|
591
|
+
(pr): ToolCallView => ({
|
|
592
|
+
id: pr.toolCallId,
|
|
593
|
+
name: pr.toolName,
|
|
594
|
+
input: undefined,
|
|
595
|
+
status: pr.isError ? 'failed' as const : 'completed' as const,
|
|
596
|
+
result: pr.isError ? undefined : contentToString(pr.content),
|
|
597
|
+
error: pr.isError ? contentToString(pr.content) : undefined,
|
|
598
|
+
}),
|
|
599
|
+
),
|
|
600
|
+
]
|
|
601
|
+
|
|
602
|
+
// Counters
|
|
603
|
+
const counters = state.agentCounters.get(agentId) ?? createDefaultCounters()
|
|
604
|
+
|
|
605
|
+
// Skills
|
|
606
|
+
const loadedSkills = state.agentSkills.get(agentId) ?? []
|
|
607
|
+
|
|
608
|
+
return {
|
|
609
|
+
id: agent.id,
|
|
610
|
+
definitionName: agent.definitionName,
|
|
611
|
+
status: toProtocolStatus(agent.status),
|
|
612
|
+
parentId: agent.parentId,
|
|
613
|
+
mailbox,
|
|
614
|
+
conversationHistory,
|
|
615
|
+
pendingToolCalls,
|
|
616
|
+
counters,
|
|
617
|
+
loadedSkills,
|
|
618
|
+
cost: agent.cost,
|
|
619
|
+
typedInput: agent.typedInput,
|
|
620
|
+
pauseReason: agent.pauseReason,
|
|
621
|
+
pauseMessage: agent.pauseMessage,
|
|
622
|
+
}
|
|
623
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Agent registry projection - tracks agent names and count.
|
|
3
|
+
*
|
|
4
|
+
* Minimal projection that replaces SessionState for name lookups.
|
|
5
|
+
* Handles only agent_spawned events.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import type { AgentId } from '@roj-ai/sdk'
|
|
9
|
+
import type { ProjectionEvent } from './events.js'
|
|
10
|
+
|
|
11
|
+
export interface AgentRegistryState {
|
|
12
|
+
names: Map<AgentId, string>
|
|
13
|
+
count: number
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export function createAgentRegistryState(): AgentRegistryState {
|
|
17
|
+
return {
|
|
18
|
+
names: new Map(),
|
|
19
|
+
count: 0,
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export function applyEventToAgentRegistry(state: AgentRegistryState, event: ProjectionEvent): AgentRegistryState {
|
|
24
|
+
if (event.type !== 'agent_spawned') return state
|
|
25
|
+
|
|
26
|
+
const newNames = new Map(state.names)
|
|
27
|
+
newNames.set(event.agentId, event.definitionName)
|
|
28
|
+
|
|
29
|
+
return {
|
|
30
|
+
names: newNames,
|
|
31
|
+
count: state.count + 1,
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export function getAgentName(state: AgentRegistryState, agentId: AgentId | string): string {
|
|
36
|
+
return state.names.get(agentId as AgentId) ?? 'unknown'
|
|
37
|
+
}
|