@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,229 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Agent tree projection - self-contained projection for building agent tree.
|
|
3
|
+
*
|
|
4
|
+
* Replaces the old buildAgentTree() that needed full SessionState.
|
|
5
|
+
* Tracks per-agent: status, parent, pending tool call count, executing flag, mailbox counts.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import type { AgentId } from '@roj-ai/sdk'
|
|
9
|
+
import type { ProjectionEvent } from './events.js'
|
|
10
|
+
import { toProtocolStatus } from './protocol-status.js'
|
|
11
|
+
import type { AgentTreeNode } from './types.js'
|
|
12
|
+
|
|
13
|
+
// ============================================================================
|
|
14
|
+
// State
|
|
15
|
+
// ============================================================================
|
|
16
|
+
|
|
17
|
+
interface AgentTreeEntry {
|
|
18
|
+
id: AgentId
|
|
19
|
+
definitionName: string
|
|
20
|
+
status: 'pending' | 'inferring' | 'tool_exec' | 'errored' | 'paused'
|
|
21
|
+
parentId: AgentId | null
|
|
22
|
+
pendingToolCallCount: number
|
|
23
|
+
isExecuting: boolean
|
|
24
|
+
mailboxUnconsumedCount: number
|
|
25
|
+
cost: number
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export interface AgentTreeProjectionState {
|
|
29
|
+
agents: Map<AgentId, AgentTreeEntry>
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export function createAgentTreeProjectionState(): AgentTreeProjectionState {
|
|
33
|
+
return { agents: new Map() }
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// ============================================================================
|
|
37
|
+
// Reducer
|
|
38
|
+
// ============================================================================
|
|
39
|
+
|
|
40
|
+
export function applyEventToAgentTree(state: AgentTreeProjectionState, event: ProjectionEvent): AgentTreeProjectionState {
|
|
41
|
+
switch (event.type) {
|
|
42
|
+
case 'agent_spawned': {
|
|
43
|
+
const newAgents = new Map(state.agents)
|
|
44
|
+
newAgents.set(event.agentId, {
|
|
45
|
+
id: event.agentId,
|
|
46
|
+
definitionName: event.definitionName,
|
|
47
|
+
status: 'pending',
|
|
48
|
+
parentId: event.parentId,
|
|
49
|
+
pendingToolCallCount: 0,
|
|
50
|
+
isExecuting: false,
|
|
51
|
+
mailboxUnconsumedCount: 0,
|
|
52
|
+
cost: 0,
|
|
53
|
+
})
|
|
54
|
+
return { ...state, agents: newAgents }
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
case 'agent_state_changed': {
|
|
58
|
+
const agent = state.agents.get(event.agentId)
|
|
59
|
+
if (!agent) return state
|
|
60
|
+
const newAgents = new Map(state.agents)
|
|
61
|
+
newAgents.set(event.agentId, { ...agent, status: event.toState })
|
|
62
|
+
return { ...state, agents: newAgents }
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
case 'inference_started': {
|
|
66
|
+
const agent = state.agents.get(event.agentId)
|
|
67
|
+
if (!agent) return state
|
|
68
|
+
const newAgents = new Map(state.agents)
|
|
69
|
+
newAgents.set(event.agentId, { ...agent, status: 'inferring' })
|
|
70
|
+
return { ...state, agents: newAgents }
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
case 'inference_completed': {
|
|
74
|
+
const agent = state.agents.get(event.agentId)
|
|
75
|
+
if (!agent) return state
|
|
76
|
+
const toolCallCount = event.response.toolCalls.length
|
|
77
|
+
const newAgents = new Map(state.agents)
|
|
78
|
+
|
|
79
|
+
// Decrement mailbox unconsumed count for consumed messages
|
|
80
|
+
let agents = newAgents
|
|
81
|
+
if (event.consumedMessageIds.length > 0) {
|
|
82
|
+
agents = decrementMailboxCounts(agents, event.agentId, event.consumedMessageIds.length)
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
agents.set(event.agentId, {
|
|
86
|
+
...agent,
|
|
87
|
+
status: toolCallCount > 0 ? 'tool_exec' : 'pending',
|
|
88
|
+
pendingToolCallCount: toolCallCount,
|
|
89
|
+
isExecuting: false,
|
|
90
|
+
cost: agent.cost + (event.metrics.cost ?? 0),
|
|
91
|
+
})
|
|
92
|
+
|
|
93
|
+
return { ...state, agents }
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
case 'inference_failed': {
|
|
97
|
+
const agent = state.agents.get(event.agentId)
|
|
98
|
+
if (!agent) return state
|
|
99
|
+
const newAgents = new Map(state.agents)
|
|
100
|
+
newAgents.set(event.agentId, { ...agent, status: 'errored' })
|
|
101
|
+
return { ...state, agents: newAgents }
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
case 'tool_started': {
|
|
105
|
+
const agent = state.agents.get(event.agentId)
|
|
106
|
+
if (!agent) return state
|
|
107
|
+
const newAgents = new Map(state.agents)
|
|
108
|
+
newAgents.set(event.agentId, { ...agent, isExecuting: true })
|
|
109
|
+
return { ...state, agents: newAgents }
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
case 'tool_completed':
|
|
113
|
+
case 'tool_failed': {
|
|
114
|
+
const agent = state.agents.get(event.agentId)
|
|
115
|
+
if (!agent) return state
|
|
116
|
+
const remaining = Math.max(0, agent.pendingToolCallCount - 1)
|
|
117
|
+
const newAgents = new Map(state.agents)
|
|
118
|
+
newAgents.set(event.agentId, {
|
|
119
|
+
...agent,
|
|
120
|
+
pendingToolCallCount: remaining,
|
|
121
|
+
isExecuting: false,
|
|
122
|
+
status: remaining === 0 ? 'pending' : 'tool_exec',
|
|
123
|
+
})
|
|
124
|
+
return { ...state, agents: newAgents }
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
case 'agent_paused': {
|
|
128
|
+
const agent = state.agents.get(event.agentId)
|
|
129
|
+
if (!agent) return state
|
|
130
|
+
const newAgents = new Map(state.agents)
|
|
131
|
+
newAgents.set(event.agentId, { ...agent, status: 'paused' })
|
|
132
|
+
return { ...state, agents: newAgents }
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
case 'agent_resumed': {
|
|
136
|
+
const agent = state.agents.get(event.agentId)
|
|
137
|
+
if (!agent) return state
|
|
138
|
+
const newAgents = new Map(state.agents)
|
|
139
|
+
newAgents.set(event.agentId, { ...agent, status: 'pending' })
|
|
140
|
+
return { ...state, agents: newAgents }
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
case 'session_restarted': {
|
|
144
|
+
const newAgents = new Map(state.agents)
|
|
145
|
+
for (const [agentId, agent] of state.agents) {
|
|
146
|
+
let updated = agent
|
|
147
|
+
let changed = false
|
|
148
|
+
|
|
149
|
+
if (agent.status === 'inferring') {
|
|
150
|
+
updated = { ...updated, status: 'pending' as const }
|
|
151
|
+
changed = true
|
|
152
|
+
}
|
|
153
|
+
if (agent.isExecuting) {
|
|
154
|
+
updated = { ...updated, isExecuting: false }
|
|
155
|
+
changed = true
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
if (changed) {
|
|
159
|
+
newAgents.set(agentId, updated)
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
return { ...state, agents: newAgents }
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
case 'mailbox_message': {
|
|
166
|
+
const agent = state.agents.get(event.toAgentId)
|
|
167
|
+
if (!agent) return state
|
|
168
|
+
const newAgents = new Map(state.agents)
|
|
169
|
+
newAgents.set(event.toAgentId, {
|
|
170
|
+
...agent,
|
|
171
|
+
mailboxUnconsumedCount: agent.mailboxUnconsumedCount + 1,
|
|
172
|
+
})
|
|
173
|
+
return { ...state, agents: newAgents }
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
case 'mailbox_consumed': {
|
|
177
|
+
const agent = state.agents.get(event.agentId)
|
|
178
|
+
if (!agent) return state
|
|
179
|
+
return {
|
|
180
|
+
...state,
|
|
181
|
+
agents: decrementMailboxCounts(new Map(state.agents), event.agentId, event.messageIds.length),
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
default:
|
|
186
|
+
return state
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
function decrementMailboxCounts(agents: Map<AgentId, AgentTreeEntry>, agentId: AgentId, count: number): Map<AgentId, AgentTreeEntry> {
|
|
191
|
+
const agent = agents.get(agentId)
|
|
192
|
+
if (!agent) return agents
|
|
193
|
+
agents.set(agentId, {
|
|
194
|
+
...agent,
|
|
195
|
+
mailboxUnconsumedCount: Math.max(0, agent.mailboxUnconsumedCount - count),
|
|
196
|
+
})
|
|
197
|
+
return agents
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
// ============================================================================
|
|
201
|
+
// Query
|
|
202
|
+
// ============================================================================
|
|
203
|
+
|
|
204
|
+
/**
|
|
205
|
+
* Build agent tree nodes from projection state.
|
|
206
|
+
*/
|
|
207
|
+
export function buildAgentTreeFromProjection(state: AgentTreeProjectionState): AgentTreeNode[] {
|
|
208
|
+
const allAgents = Array.from(state.agents.values())
|
|
209
|
+
const rootAgents = allAgents.filter((a) => a.parentId === null)
|
|
210
|
+
return rootAgents.map((agent) => buildTreeNode(state, agent))
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
function buildTreeNode(state: AgentTreeProjectionState, agent: AgentTreeEntry): AgentTreeNode {
|
|
214
|
+
const children = Array.from(state.agents.values())
|
|
215
|
+
.filter((a) => a.parentId === agent.id)
|
|
216
|
+
.map((child) => buildTreeNode(state, child))
|
|
217
|
+
|
|
218
|
+
return {
|
|
219
|
+
id: agent.id,
|
|
220
|
+
definitionName: agent.definitionName,
|
|
221
|
+
status: toProtocolStatus(agent.status),
|
|
222
|
+
parentId: agent.parentId,
|
|
223
|
+
children,
|
|
224
|
+
mailboxCount: agent.mailboxUnconsumedCount,
|
|
225
|
+
pendingToolCalls: agent.pendingToolCallCount,
|
|
226
|
+
isExecuting: agent.isExecuting,
|
|
227
|
+
cost: agent.cost,
|
|
228
|
+
}
|
|
229
|
+
}
|
|
@@ -0,0 +1,260 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Chat debug projection - tracks user-visible chat messages with debug links.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import type { AgentId, LLMCallId, ToolCallId } from '@roj-ai/sdk'
|
|
6
|
+
import type { AgentRegistryState } from './agent-registry.js'
|
|
7
|
+
import type { ProjectionEvent } from './events.js'
|
|
8
|
+
import type { DebugChatMessage } from './types.js'
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* State for tracking chat messages with debug info.
|
|
12
|
+
*/
|
|
13
|
+
export interface ChatDebugState {
|
|
14
|
+
messages: DebugChatMessage[]
|
|
15
|
+
/** Tracking pending tool calls for linking (toolCallId -> { agentId, llmCallId, toolName }) */
|
|
16
|
+
pendingToolCalls: Map<ToolCallId, { agentId: AgentId; llmCallId?: LLMCallId; toolName: string }>
|
|
17
|
+
/** Last LLM call ID by agent (for linking tool calls to inference) */
|
|
18
|
+
lastLLMCallByAgent: Map<AgentId, LLMCallId>
|
|
19
|
+
/** Event index counter */
|
|
20
|
+
eventIndex: number
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export function createChatDebugState(): ChatDebugState {
|
|
24
|
+
return {
|
|
25
|
+
messages: [],
|
|
26
|
+
pendingToolCalls: new Map(),
|
|
27
|
+
lastLLMCallByAgent: new Map(),
|
|
28
|
+
eventIndex: 0,
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Apply event to chat debug state.
|
|
34
|
+
* @param registry Agent registry for name lookups
|
|
35
|
+
*/
|
|
36
|
+
export function applyEventToChatDebug(
|
|
37
|
+
state: ChatDebugState,
|
|
38
|
+
event: ProjectionEvent,
|
|
39
|
+
registry: AgentRegistryState,
|
|
40
|
+
): ChatDebugState {
|
|
41
|
+
const currentIndex = state.eventIndex
|
|
42
|
+
const nextIndex = currentIndex + 1
|
|
43
|
+
|
|
44
|
+
const getAgentName = (agentId: AgentId): string => {
|
|
45
|
+
return registry.names.get(agentId) ?? 'unknown'
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
switch (event.type) {
|
|
49
|
+
case 'inference_completed': {
|
|
50
|
+
// Track the last LLM call for this agent
|
|
51
|
+
if (event.llmCallId) {
|
|
52
|
+
const newLastLLMCallByAgent = new Map(state.lastLLMCallByAgent)
|
|
53
|
+
newLastLLMCallByAgent.set(event.agentId, event.llmCallId)
|
|
54
|
+
return {
|
|
55
|
+
...state,
|
|
56
|
+
lastLLMCallByAgent: newLastLLMCallByAgent,
|
|
57
|
+
eventIndex: nextIndex,
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
return { ...state, eventIndex: nextIndex }
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
case 'tool_started': {
|
|
64
|
+
// Only track send_user_message and ask_user tools
|
|
65
|
+
if (event.toolName === 'send_user_message' || event.toolName === 'ask_user') {
|
|
66
|
+
const llmCallId = state.lastLLMCallByAgent.get(event.agentId)
|
|
67
|
+
const newPendingToolCalls = new Map(state.pendingToolCalls)
|
|
68
|
+
newPendingToolCalls.set(event.toolCallId, {
|
|
69
|
+
agentId: event.agentId,
|
|
70
|
+
llmCallId,
|
|
71
|
+
toolName: event.toolName,
|
|
72
|
+
})
|
|
73
|
+
return {
|
|
74
|
+
...state,
|
|
75
|
+
pendingToolCalls: newPendingToolCalls,
|
|
76
|
+
eventIndex: nextIndex,
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
return { ...state, eventIndex: nextIndex }
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
case 'user_message_sent': {
|
|
83
|
+
// Find the pending tool call for this message
|
|
84
|
+
let toolCallId: ToolCallId | undefined
|
|
85
|
+
let llmCallId: LLMCallId | undefined
|
|
86
|
+
let agentId: AgentId | undefined
|
|
87
|
+
|
|
88
|
+
// Look through pending tool calls to find a send_user_message from the same agent
|
|
89
|
+
for (const [tcId, info] of state.pendingToolCalls) {
|
|
90
|
+
if (info.toolName === 'send_user_message' && info.agentId === event.agentId) {
|
|
91
|
+
toolCallId = tcId
|
|
92
|
+
llmCallId = info.llmCallId
|
|
93
|
+
agentId = info.agentId
|
|
94
|
+
break
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// Clean up the used pending tool call
|
|
99
|
+
const newPendingToolCalls = new Map(state.pendingToolCalls)
|
|
100
|
+
if (toolCallId) {
|
|
101
|
+
newPendingToolCalls.delete(toolCallId)
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
const newMessage: DebugChatMessage = {
|
|
105
|
+
type: 'agent_message',
|
|
106
|
+
messageId: event.messageId,
|
|
107
|
+
content: event.message,
|
|
108
|
+
timestamp: event.timestamp,
|
|
109
|
+
eventIndex: currentIndex,
|
|
110
|
+
agentId: agentId ?? event.agentId,
|
|
111
|
+
agentName: getAgentName(agentId ?? event.agentId),
|
|
112
|
+
llmCallId,
|
|
113
|
+
toolCallId,
|
|
114
|
+
format: event.format,
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
return {
|
|
118
|
+
...state,
|
|
119
|
+
messages: [...state.messages, newMessage],
|
|
120
|
+
pendingToolCalls: newPendingToolCalls,
|
|
121
|
+
eventIndex: nextIndex,
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
case 'user_question_asked': {
|
|
126
|
+
// Find the pending tool call for this question
|
|
127
|
+
let toolCallId: ToolCallId | undefined
|
|
128
|
+
let llmCallId: LLMCallId | undefined
|
|
129
|
+
let agentId: AgentId | undefined
|
|
130
|
+
|
|
131
|
+
// Look through pending tool calls to find an ask_user from the same agent
|
|
132
|
+
for (const [tcId, info] of state.pendingToolCalls) {
|
|
133
|
+
if (info.toolName === 'ask_user' && info.agentId === event.agentId) {
|
|
134
|
+
toolCallId = tcId
|
|
135
|
+
llmCallId = info.llmCallId
|
|
136
|
+
agentId = info.agentId
|
|
137
|
+
break
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
// Clean up the used pending tool call
|
|
142
|
+
const newPendingToolCalls = new Map(state.pendingToolCalls)
|
|
143
|
+
if (toolCallId) {
|
|
144
|
+
newPendingToolCalls.delete(toolCallId)
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
const newMessage: DebugChatMessage = {
|
|
148
|
+
type: 'ask_user',
|
|
149
|
+
messageId: event.messageId,
|
|
150
|
+
content: event.question,
|
|
151
|
+
timestamp: event.timestamp,
|
|
152
|
+
eventIndex: currentIndex,
|
|
153
|
+
agentId: agentId ?? event.agentId,
|
|
154
|
+
agentName: getAgentName(agentId ?? event.agentId),
|
|
155
|
+
llmCallId,
|
|
156
|
+
toolCallId,
|
|
157
|
+
inputType: event.inputType,
|
|
158
|
+
answered: false,
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
return {
|
|
162
|
+
...state,
|
|
163
|
+
messages: [...state.messages, newMessage],
|
|
164
|
+
pendingToolCalls: newPendingToolCalls,
|
|
165
|
+
eventIndex: nextIndex,
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
case 'user_chat_message_received': {
|
|
170
|
+
const newMessage: DebugChatMessage = {
|
|
171
|
+
type: 'user_message',
|
|
172
|
+
messageId: event.messageId,
|
|
173
|
+
content: event.content,
|
|
174
|
+
timestamp: event.timestamp,
|
|
175
|
+
eventIndex: currentIndex,
|
|
176
|
+
agentId: event.agentId,
|
|
177
|
+
agentName: getAgentName(event.agentId),
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
return {
|
|
181
|
+
...state,
|
|
182
|
+
messages: [...state.messages, newMessage],
|
|
183
|
+
eventIndex: nextIndex,
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
case 'user_chat_answer_received': {
|
|
188
|
+
const updatedMessages = state.messages.map((msg) => {
|
|
189
|
+
if (msg.type === 'ask_user' && msg.messageId === event.questionId) {
|
|
190
|
+
return {
|
|
191
|
+
...msg,
|
|
192
|
+
answered: true,
|
|
193
|
+
answer: event.answerValue,
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
return msg
|
|
197
|
+
})
|
|
198
|
+
|
|
199
|
+
return {
|
|
200
|
+
...state,
|
|
201
|
+
messages: updatedMessages,
|
|
202
|
+
eventIndex: nextIndex,
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
case 'mailbox_message': {
|
|
207
|
+
// Only handle user messages (not answers to questions)
|
|
208
|
+
if (event.message.from === 'user' && !event.message.answerTo) {
|
|
209
|
+
const newMessage: DebugChatMessage = {
|
|
210
|
+
type: 'user_message',
|
|
211
|
+
messageId: event.message.id,
|
|
212
|
+
content: event.message.content,
|
|
213
|
+
timestamp: event.message.timestamp,
|
|
214
|
+
eventIndex: currentIndex,
|
|
215
|
+
agentId: event.toAgentId,
|
|
216
|
+
agentName: getAgentName(event.toAgentId),
|
|
217
|
+
mailboxMessageId: event.message.id,
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
return {
|
|
221
|
+
...state,
|
|
222
|
+
messages: [...state.messages, newMessage],
|
|
223
|
+
eventIndex: nextIndex,
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
// Handle answer to question - update the ask_user message
|
|
228
|
+
if (event.message.from === 'user' && event.message.answerTo) {
|
|
229
|
+
const updatedMessages = state.messages.map((msg) => {
|
|
230
|
+
if (msg.type === 'ask_user' && msg.messageId === event.message.answerTo) {
|
|
231
|
+
return {
|
|
232
|
+
...msg,
|
|
233
|
+
answered: true,
|
|
234
|
+
answer: event.message.answerValue,
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
return msg
|
|
238
|
+
})
|
|
239
|
+
|
|
240
|
+
return {
|
|
241
|
+
...state,
|
|
242
|
+
messages: updatedMessages,
|
|
243
|
+
eventIndex: nextIndex,
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
return { ...state, eventIndex: nextIndex }
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
default:
|
|
251
|
+
return { ...state, eventIndex: nextIndex }
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
/**
|
|
256
|
+
* Get chat debug messages sorted by timestamp.
|
|
257
|
+
*/
|
|
258
|
+
export function getChatDebugMessages(state: ChatDebugState): DebugChatMessage[] {
|
|
259
|
+
return state.messages
|
|
260
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ProjectionEvent — union of all event types handled by shared projections.
|
|
3
|
+
*
|
|
4
|
+
* Boundary code (event-store.ts, debug.ts) casts DomainEvent → ProjectionEvent once.
|
|
5
|
+
* Individual projection reducers accept ProjectionEvent directly — no internal casts.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import type { BuiltinEvent, ServiceStatusChangedEvent, SkillLoadedEvent } from '@roj-ai/sdk'
|
|
9
|
+
|
|
10
|
+
export type ProjectionEvent = BuiltinEvent | SkillLoadedEvent | ServiceStatusChangedEvent
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
// Projection event type
|
|
2
|
+
export type { ProjectionEvent } from './events.js'
|
|
3
|
+
|
|
4
|
+
// Types
|
|
5
|
+
export type {
|
|
6
|
+
AgentTreeNode,
|
|
7
|
+
AssistantConversationMessageView,
|
|
8
|
+
ConversationMessageView,
|
|
9
|
+
DebugChatMessage,
|
|
10
|
+
GetAgentDetailResponse,
|
|
11
|
+
GetEventsResponse,
|
|
12
|
+
GetMetricsResponse,
|
|
13
|
+
GlobalMailboxMessage,
|
|
14
|
+
MailboxMessageView,
|
|
15
|
+
SystemConversationMessageView,
|
|
16
|
+
TimelineItem,
|
|
17
|
+
ToolCallView,
|
|
18
|
+
ToolConversationMessageView,
|
|
19
|
+
UserConversationMessageView,
|
|
20
|
+
} from './types.js'
|
|
21
|
+
|
|
22
|
+
// Protocol status
|
|
23
|
+
export { toProtocolStatus } from './protocol-status.js'
|
|
24
|
+
|
|
25
|
+
// Agent registry
|
|
26
|
+
export type { AgentRegistryState } from './agent-registry.js'
|
|
27
|
+
export { applyEventToAgentRegistry, createAgentRegistryState, getAgentName } from './agent-registry.js'
|
|
28
|
+
|
|
29
|
+
// Agent tree projection (self-contained — no SessionState)
|
|
30
|
+
export type { AgentTreeProjectionState } from './agent-tree-projection.js'
|
|
31
|
+
export { applyEventToAgentTree, buildAgentTreeFromProjection, createAgentTreeProjectionState } from './agent-tree-projection.js'
|
|
32
|
+
|
|
33
|
+
// Metrics
|
|
34
|
+
export type { MetricsState } from './metrics.js'
|
|
35
|
+
export { applyEventToMetrics, createMetricsState, metricsStateToResponse } from './metrics.js'
|
|
36
|
+
|
|
37
|
+
// Timeline
|
|
38
|
+
export type { TimelineState } from './timeline.js'
|
|
39
|
+
export { applyEventToTimeline, createTimelineState, getTimelineItems } from './timeline.js'
|
|
40
|
+
|
|
41
|
+
// Mailbox projection
|
|
42
|
+
export type { MailboxState } from './mailbox.js'
|
|
43
|
+
export { applyEventToMailbox, createMailboxState, getMailboxMessages } from './mailbox.js'
|
|
44
|
+
|
|
45
|
+
// Chat debug
|
|
46
|
+
export type { ChatDebugState } from './chat-debug.js'
|
|
47
|
+
export { applyEventToChatDebug, createChatDebugState, getChatDebugMessages } from './chat-debug.js'
|
|
48
|
+
|
|
49
|
+
// Agent detail projection (self-contained — no SessionState)
|
|
50
|
+
export type { AgentDetailProjectionState } from './agent-detail-projection.js'
|
|
51
|
+
export { applyEventToAgentDetail, createAgentDetailProjectionState, getAgentDetail } from './agent-detail-projection.js'
|
|
52
|
+
|
|
53
|
+
// Session info projection
|
|
54
|
+
export type { SessionInfoState } from './session-info.js'
|
|
55
|
+
export { applyEventToSessionInfo, createSessionInfoState } from './session-info.js'
|
|
56
|
+
|
|
57
|
+
// Services projection
|
|
58
|
+
export type { ServiceEntry, ServicesProjectionState, ServiceStatus } from './services-projection.js'
|
|
59
|
+
export { applyEventToServices, createServicesProjectionState } from './services-projection.js'
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Global mailbox projection - tracks all inter-agent messages.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import type { AgentId } from '@roj-ai/sdk'
|
|
6
|
+
import type { AgentRegistryState } from './agent-registry.js'
|
|
7
|
+
import type { ProjectionEvent } from './events.js'
|
|
8
|
+
import type { GlobalMailboxMessage } from './types.js'
|
|
9
|
+
|
|
10
|
+
export interface MailboxState {
|
|
11
|
+
messages: GlobalMailboxMessage[]
|
|
12
|
+
consumedIds: Set<string>
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export function createMailboxState(): MailboxState {
|
|
16
|
+
return {
|
|
17
|
+
messages: [],
|
|
18
|
+
consumedIds: new Set(),
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Apply event to mailbox state.
|
|
24
|
+
* @param registry Agent registry for name lookups
|
|
25
|
+
*/
|
|
26
|
+
export function applyEventToMailbox(
|
|
27
|
+
state: MailboxState,
|
|
28
|
+
event: ProjectionEvent,
|
|
29
|
+
registry: AgentRegistryState,
|
|
30
|
+
): MailboxState {
|
|
31
|
+
switch (event.type) {
|
|
32
|
+
case 'mailbox_message': {
|
|
33
|
+
const msg = event.message
|
|
34
|
+
|
|
35
|
+
// Determine from agent name
|
|
36
|
+
let fromAgentId: string
|
|
37
|
+
let fromAgentName: string
|
|
38
|
+
|
|
39
|
+
if (msg.from === 'user') {
|
|
40
|
+
fromAgentId = 'user'
|
|
41
|
+
fromAgentName = 'User'
|
|
42
|
+
} else if (msg.from === 'orchestrator' || msg.from === 'communicator') {
|
|
43
|
+
fromAgentId = msg.from
|
|
44
|
+
fromAgentName = msg.from.charAt(0).toUpperCase() + msg.from.slice(1)
|
|
45
|
+
} else {
|
|
46
|
+
fromAgentId = msg.from
|
|
47
|
+
fromAgentName = registry.names.get(msg.from as AgentId) ?? 'unknown'
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
const newMessage: GlobalMailboxMessage = {
|
|
51
|
+
id: msg.id,
|
|
52
|
+
fromAgentId,
|
|
53
|
+
fromAgentName,
|
|
54
|
+
toAgentId: event.toAgentId,
|
|
55
|
+
toAgentName: registry.names.get(event.toAgentId) ?? 'unknown',
|
|
56
|
+
content: msg.content,
|
|
57
|
+
timestamp: msg.timestamp,
|
|
58
|
+
consumed: state.consumedIds.has(msg.id),
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
return {
|
|
62
|
+
...state,
|
|
63
|
+
messages: [...state.messages, newMessage],
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
case 'mailbox_consumed': {
|
|
68
|
+
const newConsumedIds = new Set(state.consumedIds)
|
|
69
|
+
for (const msgId of event.messageIds) {
|
|
70
|
+
newConsumedIds.add(msgId)
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// Update consumed status in existing messages
|
|
74
|
+
const consumedSet = new Set(event.messageIds as string[])
|
|
75
|
+
const updatedMessages = state.messages.map((m) => consumedSet.has(m.id) ? { ...m, consumed: true } : m)
|
|
76
|
+
|
|
77
|
+
return {
|
|
78
|
+
messages: updatedMessages,
|
|
79
|
+
consumedIds: newConsumedIds,
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
case 'inference_completed': {
|
|
84
|
+
// inference_completed also marks messages as consumed
|
|
85
|
+
if (event.consumedMessageIds.length === 0) return state
|
|
86
|
+
|
|
87
|
+
const newConsumedIds = new Set(state.consumedIds)
|
|
88
|
+
for (const msgId of event.consumedMessageIds) {
|
|
89
|
+
newConsumedIds.add(msgId)
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
// Update consumed status in existing messages
|
|
93
|
+
const consumedSet = new Set(event.consumedMessageIds as string[])
|
|
94
|
+
const updatedMessages = state.messages.map((m) => consumedSet.has(m.id) ? { ...m, consumed: true } : m)
|
|
95
|
+
|
|
96
|
+
return {
|
|
97
|
+
messages: updatedMessages,
|
|
98
|
+
consumedIds: newConsumedIds,
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
default:
|
|
103
|
+
return state
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* Get mailbox messages sorted by timestamp.
|
|
109
|
+
*/
|
|
110
|
+
export function getMailboxMessages(state: MailboxState): GlobalMailboxMessage[] {
|
|
111
|
+
// Messages are already in order since we append them
|
|
112
|
+
return state.messages
|
|
113
|
+
}
|