@aphrody/m3-ai 3.3.2 → 3.3.5
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/README.md +29 -7
- package/dist/A2APeerInspector.d.ts +41 -0
- package/dist/A2APeerInspector.d.ts.map +1 -0
- package/dist/A2ATaskTimeline.d.ts +28 -0
- package/dist/A2ATaskTimeline.d.ts.map +1 -0
- package/dist/AgUiEventStream.d.ts +87 -0
- package/dist/AgUiEventStream.d.ts.map +1 -0
- package/dist/GeminiAccountMenu.d.ts +55 -0
- package/dist/GeminiAccountMenu.d.ts.map +1 -0
- package/dist/GeminiPersonalContextEdit.d.ts +29 -0
- package/dist/GeminiPersonalContextEdit.d.ts.map +1 -0
- package/dist/GoogleSpotlightBar.d.ts +32 -0
- package/dist/GoogleSpotlightBar.d.ts.map +1 -0
- package/dist/Markdown.d.ts +2 -1
- package/dist/Markdown.d.ts.map +1 -1
- package/dist/McpAppCard.d.ts +24 -0
- package/dist/McpAppCard.d.ts.map +1 -0
- package/dist/ModelSelector.d.ts.map +1 -1
- package/dist/TaskRow.d.ts.map +1 -1
- package/dist/ToolCall.d.ts.map +1 -1
- package/dist/index.d.ts +15 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3629 -1950
- package/dist/index.js.map +32 -1
- package/package.json +17 -20
- package/src/A2APeerInspector.tsx +370 -0
- package/src/A2ATaskTimeline.tsx +218 -0
- package/src/AgUiEventStream.ts +492 -0
- package/src/ChatComposer.tsx +261 -0
- package/src/ConversationList.tsx +207 -0
- package/src/GeminiAccountMenu.tsx +212 -0
- package/src/GeminiPersonalContextEdit.tsx +147 -0
- package/src/GoogleSpotlightBar.tsx +165 -0
- package/src/InlineBranchCard.tsx +97 -0
- package/src/InlineWorkerCard.tsx +182 -0
- package/src/Markdown.tsx +769 -0
- package/src/McpAppCard.tsx +161 -0
- package/src/MemoryPanel.tsx +176 -0
- package/src/MessageBubble.tsx +143 -0
- package/src/ModelSelector.tsx +319 -0
- package/src/ModelUnavailable.tsx +116 -0
- package/src/TaskCreateForm.tsx +123 -0
- package/src/TaskDetail.tsx +224 -0
- package/src/TaskList.tsx +136 -0
- package/src/TaskPriorityIcon.tsx +39 -0
- package/src/TaskRow.tsx +177 -0
- package/src/TaskStatusIcon.tsx +39 -0
- package/src/ToolCall.tsx +644 -0
- package/src/chatIcons.tsx +105 -0
- package/src/chatLabels.ts +243 -0
- package/src/citations.tsx +286 -0
- package/src/index.ts +173 -0
- package/src/internal.tsx +107 -0
- package/src/openaiChatStream.ts +400 -0
- package/src/types.ts +212 -0
- package/src/useOpenAIChat.ts +126 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@aphrody/m3-ai",
|
|
3
|
-
"version": "3.3.
|
|
3
|
+
"version": "3.3.5",
|
|
4
4
|
"description": "Material Design 3 agent-interaction components (tool calls, markdown, chat composer, workers, tasks): the SpaceUI (@spacedrive/ai) set rebuilt on @aphrody/m3-primitives.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"material-3",
|
|
@@ -13,16 +13,19 @@
|
|
|
13
13
|
"author": "aphrody-code",
|
|
14
14
|
"files": [
|
|
15
15
|
"dist",
|
|
16
|
-
"LICENSE.spaceui"
|
|
16
|
+
"LICENSE.spaceui",
|
|
17
|
+
"src"
|
|
17
18
|
],
|
|
18
19
|
"type": "module",
|
|
19
20
|
"sideEffects": false,
|
|
20
|
-
"main": "./
|
|
21
|
-
"module": "./
|
|
22
|
-
"types": "./
|
|
21
|
+
"main": "./src/index.ts",
|
|
22
|
+
"module": "./src/index.ts",
|
|
23
|
+
"types": "./src/index.ts",
|
|
23
24
|
"exports": {
|
|
24
25
|
".": {
|
|
25
|
-
"
|
|
26
|
+
"bun": "./src/index.ts",
|
|
27
|
+
"import": "./src/index.ts",
|
|
28
|
+
"types": "./src/index.ts",
|
|
26
29
|
"default": "./dist/index.js"
|
|
27
30
|
}
|
|
28
31
|
},
|
|
@@ -30,30 +33,24 @@
|
|
|
30
33
|
"access": "public"
|
|
31
34
|
},
|
|
32
35
|
"scripts": {
|
|
33
|
-
"build": "
|
|
36
|
+
"build": "bun ../../scripts/build.ts build --only $npm_package_name",
|
|
34
37
|
"typecheck": "tsc --noEmit",
|
|
35
|
-
"typecheck:fast": "
|
|
38
|
+
"typecheck:fast": "tsc --noEmit",
|
|
36
39
|
"test": "bun test"
|
|
37
40
|
},
|
|
38
41
|
"dependencies": {
|
|
39
|
-
"@aphrody/
|
|
40
|
-
"@aphrody/m3-
|
|
42
|
+
"@aphrody/ag-ui": "1.0.1",
|
|
43
|
+
"@aphrody/m3-motion": "3.3.5",
|
|
44
|
+
"@aphrody/m3-primitives": "3.3.5",
|
|
45
|
+
"@aphrody/mcp-ui": "7.1.1-aphrody.1",
|
|
41
46
|
"clsx": "^2.1.1",
|
|
42
|
-
"motion": "^
|
|
47
|
+
"motion": "^13.4.4",
|
|
43
48
|
"react-markdown": "^10.1.0",
|
|
44
49
|
"rehype-raw": "^7.0.0",
|
|
45
50
|
"remark-gfm": "^4.0.1"
|
|
46
51
|
},
|
|
47
52
|
"devDependencies": {
|
|
48
|
-
"@aphrody/m3-config": "3.3.
|
|
49
|
-
"@happy-dom/global-registrator": "^20.14.5",
|
|
50
|
-
"@types/react": "^19.3.0",
|
|
51
|
-
"@types/react-dom": "^19.3.0",
|
|
52
|
-
"bun-types": "^1.4.2",
|
|
53
|
-
"react": "^19.3.0",
|
|
54
|
-
"react-dom": "^19.3.0",
|
|
55
|
-
"tsup": "^8.5.1",
|
|
56
|
-
"typescript": "^7.0.2"
|
|
53
|
+
"@aphrody/m3-config": "3.3.5"
|
|
57
54
|
},
|
|
58
55
|
"peerDependencies": {
|
|
59
56
|
"react": ">=18.0.0",
|
|
@@ -0,0 +1,370 @@
|
|
|
1
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
2
|
+
/**
|
|
3
|
+
* @aphrody/m3-ai - A2APeerInspector
|
|
4
|
+
*
|
|
5
|
+
* Material Design 3 Agent-to-Agent (A2A 1.0) Fleet Inspector:
|
|
6
|
+
* - Visualizes connected peer agents (codex, claude, agycli, nie, ocr, rag, ...).
|
|
7
|
+
* - Displays AgentCard interface, capabilities, and active tasks.
|
|
8
|
+
* - Live inspection of .coord mailbox envelopes (ask, fact, ack, ping, error).
|
|
9
|
+
* - File lock & resource claims view (.coord/claims).
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import React, { useState } from "react";
|
|
13
|
+
import { clsx } from "clsx";
|
|
14
|
+
import type { CoordEnvelope, EnvelopeKind } from "./AgUiEventStream";
|
|
15
|
+
|
|
16
|
+
export interface AgentPeer {
|
|
17
|
+
id: string;
|
|
18
|
+
name: string;
|
|
19
|
+
role: string;
|
|
20
|
+
status: "idle" | "busy" | "offline";
|
|
21
|
+
version?: string;
|
|
22
|
+
skills?: string[];
|
|
23
|
+
activeClaim?: string;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export interface SendEnvelopePayload {
|
|
27
|
+
from: string;
|
|
28
|
+
to?: string;
|
|
29
|
+
kind: EnvelopeKind | string;
|
|
30
|
+
topic: string;
|
|
31
|
+
body: string;
|
|
32
|
+
re?: string;
|
|
33
|
+
channel_hint?: string[];
|
|
34
|
+
v?: number;
|
|
35
|
+
[key: string]: unknown;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export interface A2APeerInspectorProps {
|
|
39
|
+
peers?: AgentPeer[];
|
|
40
|
+
envelopes?: CoordEnvelope[];
|
|
41
|
+
selectedPeerId?: string;
|
|
42
|
+
onSelectPeer?: (peerId: string) => void;
|
|
43
|
+
onSendEnvelope?: (envelope: SendEnvelopePayload) => void;
|
|
44
|
+
className?: string;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
const EMPTY_ENVELOPES: CoordEnvelope[] = [];
|
|
48
|
+
|
|
49
|
+
const DEFAULT_PEERS: AgentPeer[] = [
|
|
50
|
+
{
|
|
51
|
+
id: "agy",
|
|
52
|
+
name: "Antigravity Core",
|
|
53
|
+
role: "Integrator & UI / Rust Lead",
|
|
54
|
+
status: "busy",
|
|
55
|
+
version: "2.0.0",
|
|
56
|
+
skills: ["m3-ui", "rust-serial", "bun-native", "a2a-coord"],
|
|
57
|
+
activeClaim: ".coord/claims/native-integration.json",
|
|
58
|
+
},
|
|
59
|
+
{
|
|
60
|
+
id: "codex",
|
|
61
|
+
name: "Codex Peer",
|
|
62
|
+
role: "Backend & Systems Worker",
|
|
63
|
+
status: "busy",
|
|
64
|
+
version: "1.4.0",
|
|
65
|
+
skills: ["native-wiring", "wsl-probe", "kernel-ffi"],
|
|
66
|
+
},
|
|
67
|
+
{
|
|
68
|
+
id: "claude",
|
|
69
|
+
name: "Claude Peer",
|
|
70
|
+
role: "Architecture & Verification",
|
|
71
|
+
status: "idle",
|
|
72
|
+
version: "3.7.0",
|
|
73
|
+
skills: ["design-tokens", "docs-audit", "review"],
|
|
74
|
+
},
|
|
75
|
+
];
|
|
76
|
+
|
|
77
|
+
const KIND_COLORS: Record<EnvelopeKind, { bg: string; text: string; border: string }> = {
|
|
78
|
+
ask: {
|
|
79
|
+
bg: "bg-[var(--md-sys-color-primary-container,#eaddff)]",
|
|
80
|
+
text: "text-[var(--md-sys-color-on-primary-container,#21005d)]",
|
|
81
|
+
border: "border-[var(--md-sys-color-primary,#6750a4)]",
|
|
82
|
+
},
|
|
83
|
+
fact: {
|
|
84
|
+
bg: "bg-[var(--md-sys-color-tertiary-container,#ffd8e4)]",
|
|
85
|
+
text: "text-[var(--md-sys-color-on-tertiary-container,#31111d)]",
|
|
86
|
+
border: "border-[var(--md-sys-color-tertiary,#7d5260)]",
|
|
87
|
+
},
|
|
88
|
+
ack: {
|
|
89
|
+
bg: "bg-[var(--md-sys-color-secondary-container,#e8def8)]",
|
|
90
|
+
text: "text-[var(--md-sys-color-on-secondary-container,#1d192b)]",
|
|
91
|
+
border: "border-[var(--md-sys-color-secondary,#625b71)]",
|
|
92
|
+
},
|
|
93
|
+
ping: {
|
|
94
|
+
bg: "bg-[var(--md-sys-color-surface-container-high,#ece6f0)]",
|
|
95
|
+
text: "text-[var(--md-sys-color-on-surface-variant,#49454f)]",
|
|
96
|
+
border: "border-[var(--md-sys-color-outline-variant,#cac4d0)]",
|
|
97
|
+
},
|
|
98
|
+
error: {
|
|
99
|
+
bg: "bg-[var(--md-sys-color-error-container,#f9dedc)]",
|
|
100
|
+
text: "text-[var(--md-sys-color-on-error-container,#410e0b)]",
|
|
101
|
+
border: "border-[var(--md-sys-color-error,#b3261e)]",
|
|
102
|
+
},
|
|
103
|
+
};
|
|
104
|
+
|
|
105
|
+
export const A2APeerInspector: React.FC<A2APeerInspectorProps> = ({
|
|
106
|
+
peers = DEFAULT_PEERS,
|
|
107
|
+
envelopes = EMPTY_ENVELOPES,
|
|
108
|
+
selectedPeerId: controlledPeerId,
|
|
109
|
+
onSelectPeer,
|
|
110
|
+
onSendEnvelope,
|
|
111
|
+
className,
|
|
112
|
+
}) => {
|
|
113
|
+
const [internalSelectedPeerId, setInternalSelectedPeerId] = useState<string>(
|
|
114
|
+
peers[0]?.id || "agy",
|
|
115
|
+
);
|
|
116
|
+
const selectedPeerId = controlledPeerId ?? internalSelectedPeerId;
|
|
117
|
+
|
|
118
|
+
// Form state for composing envelope
|
|
119
|
+
const [composeTopic, setComposeTopic] = useState("");
|
|
120
|
+
const [composeBody, setComposeBody] = useState("");
|
|
121
|
+
const [composeKind, setComposeKind] = useState<EnvelopeKind>("ask");
|
|
122
|
+
|
|
123
|
+
const currentPeer = peers.find((p) => p.id === selectedPeerId) || peers[0];
|
|
124
|
+
|
|
125
|
+
const handleSelect = (peerId: string) => {
|
|
126
|
+
setInternalSelectedPeerId(peerId);
|
|
127
|
+
onSelectPeer?.(peerId);
|
|
128
|
+
};
|
|
129
|
+
|
|
130
|
+
const handleSend = (e: React.FormEvent) => {
|
|
131
|
+
e.preventDefault();
|
|
132
|
+
if (!composeTopic.trim() || !composeBody.trim()) return;
|
|
133
|
+
|
|
134
|
+
onSendEnvelope?.({
|
|
135
|
+
from: "agy",
|
|
136
|
+
to: selectedPeerId,
|
|
137
|
+
kind: composeKind,
|
|
138
|
+
topic: composeTopic.trim(),
|
|
139
|
+
body: composeBody.trim(),
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
setComposeTopic("");
|
|
143
|
+
setComposeBody("");
|
|
144
|
+
};
|
|
145
|
+
|
|
146
|
+
const filteredEnvelopes = envelopes.filter(
|
|
147
|
+
(env) => env.from === selectedPeerId || env.to === selectedPeerId || env.to === "fleet",
|
|
148
|
+
);
|
|
149
|
+
|
|
150
|
+
return (
|
|
151
|
+
<div
|
|
152
|
+
className={clsx(
|
|
153
|
+
"flex flex-col gap-4 rounded-3xl p-5 border",
|
|
154
|
+
"bg-[var(--md-sys-color-surface-container-lowest,#ffffff)]",
|
|
155
|
+
"border-[var(--md-sys-color-outline-variant,#cac4d0)]",
|
|
156
|
+
"text-[var(--md-sys-color-on-surface,#1d1b20)]",
|
|
157
|
+
className,
|
|
158
|
+
)}
|
|
159
|
+
>
|
|
160
|
+
{/* Header & Peer Chips */}
|
|
161
|
+
<div className="flex flex-col gap-3">
|
|
162
|
+
<div className="flex items-center justify-between">
|
|
163
|
+
<div className="flex items-center gap-2">
|
|
164
|
+
<span className="material-symbols-outlined text-xl text-[var(--md-sys-color-primary,#6750a4)]">
|
|
165
|
+
hub
|
|
166
|
+
</span>
|
|
167
|
+
<h2 className="text-base font-semibold tracking-tight">A2A Fleet Coordinator</h2>
|
|
168
|
+
<span className="rounded-full bg-[var(--md-sys-color-surface-container-high,#ece6f0)] px-2 py-0.5 text-xs font-mono text-[var(--md-sys-color-on-surface-variant,#49454f)]">
|
|
169
|
+
v1.0 (JSONL + RPC)
|
|
170
|
+
</span>
|
|
171
|
+
</div>
|
|
172
|
+
<span className="text-xs text-[var(--md-sys-color-outline,#79747e)]">
|
|
173
|
+
{peers.length} active peers
|
|
174
|
+
</span>
|
|
175
|
+
</div>
|
|
176
|
+
|
|
177
|
+
{/* Peer selection chips */}
|
|
178
|
+
<div className="flex items-center gap-2 overflow-x-auto pb-1">
|
|
179
|
+
{peers.map((peer) => {
|
|
180
|
+
const isSelected = peer.id === selectedPeerId;
|
|
181
|
+
return (
|
|
182
|
+
<button
|
|
183
|
+
key={peer.id}
|
|
184
|
+
type="button"
|
|
185
|
+
onClick={() => handleSelect(peer.id)}
|
|
186
|
+
className={clsx(
|
|
187
|
+
"flex items-center gap-2 rounded-full px-3.5 py-1.5 text-xs font-medium transition-colors shrink-0",
|
|
188
|
+
isSelected
|
|
189
|
+
? "bg-[var(--md-sys-color-secondary-container,#e8def8)] text-[var(--md-sys-color-on-secondary-container,#1d192b)] shadow-sm"
|
|
190
|
+
: "bg-[var(--md-sys-color-surface-container,#f3edf7)] text-[var(--md-sys-color-on-surface-variant,#49454f)] hover:bg-[var(--md-sys-color-surface-container-high,#ece6f0)]",
|
|
191
|
+
)}
|
|
192
|
+
>
|
|
193
|
+
<span
|
|
194
|
+
className={clsx(
|
|
195
|
+
"h-2 w-2 rounded-full",
|
|
196
|
+
peer.status === "busy"
|
|
197
|
+
? "bg-amber-500 animate-pulse"
|
|
198
|
+
: peer.status === "idle"
|
|
199
|
+
? "bg-emerald-500"
|
|
200
|
+
: "bg-slate-400",
|
|
201
|
+
)}
|
|
202
|
+
/>
|
|
203
|
+
<span>{peer.name}</span>
|
|
204
|
+
<span className="text-[10px] opacity-60">@{peer.id}</span>
|
|
205
|
+
</button>
|
|
206
|
+
);
|
|
207
|
+
})}
|
|
208
|
+
</div>
|
|
209
|
+
</div>
|
|
210
|
+
|
|
211
|
+
{/* Selected Peer Details Card */}
|
|
212
|
+
{currentPeer && (
|
|
213
|
+
<div className="flex flex-col gap-2 rounded-2xl bg-[var(--md-sys-color-surface-container-low,#f7f2fa)] p-4 border border-[var(--md-sys-color-outline-variant,#cac4d0)]/50">
|
|
214
|
+
<div className="flex items-start justify-between">
|
|
215
|
+
<div>
|
|
216
|
+
<div className="flex items-center gap-2">
|
|
217
|
+
<span className="font-semibold text-sm">{currentPeer.name}</span>
|
|
218
|
+
<span className="text-xs text-[var(--md-sys-color-on-surface-variant,#49454f)]">
|
|
219
|
+
({currentPeer.role})
|
|
220
|
+
</span>
|
|
221
|
+
</div>
|
|
222
|
+
<div className="text-xs text-[var(--md-sys-color-outline,#79747e)] mt-0.5">
|
|
223
|
+
Peer ID: <code className="font-mono text-xs">{currentPeer.id}</code> · Version:{" "}
|
|
224
|
+
{currentPeer.version || "1.0.0"}
|
|
225
|
+
</div>
|
|
226
|
+
</div>
|
|
227
|
+
{currentPeer.activeClaim && (
|
|
228
|
+
<div className="flex items-center gap-1.5 rounded-lg bg-amber-500/10 px-2 py-1 text-xs text-amber-700 dark:text-amber-400 border border-amber-500/20">
|
|
229
|
+
<span className="material-symbols-outlined text-sm">lock</span>
|
|
230
|
+
<span className="font-mono text-[11px]">{currentPeer.activeClaim}</span>
|
|
231
|
+
</div>
|
|
232
|
+
)}
|
|
233
|
+
</div>
|
|
234
|
+
|
|
235
|
+
{currentPeer.skills && currentPeer.skills.length > 0 && (
|
|
236
|
+
<div className="flex flex-wrap gap-1.5 mt-1">
|
|
237
|
+
{currentPeer.skills.map((skill) => (
|
|
238
|
+
<span
|
|
239
|
+
key={skill}
|
|
240
|
+
className="rounded-md bg-[var(--md-sys-color-surface-container-highest,#e6e0e9)] px-2 py-0.5 text-[11px] font-mono text-[var(--md-sys-color-on-surface-variant,#49454f)]"
|
|
241
|
+
>
|
|
242
|
+
#{skill}
|
|
243
|
+
</span>
|
|
244
|
+
))}
|
|
245
|
+
</div>
|
|
246
|
+
)}
|
|
247
|
+
</div>
|
|
248
|
+
)}
|
|
249
|
+
|
|
250
|
+
{/* Envelope Stream Section */}
|
|
251
|
+
<div className="flex flex-col gap-2">
|
|
252
|
+
<div className="flex items-center justify-between">
|
|
253
|
+
<h3 className="text-xs font-semibold uppercase tracking-wider text-[var(--md-sys-color-on-surface-variant,#49454f)]">
|
|
254
|
+
Coordination Envelopes ({filteredEnvelopes.length})
|
|
255
|
+
</h3>
|
|
256
|
+
<span className="text-[11px] text-[var(--md-sys-color-outline,#79747e)]">
|
|
257
|
+
.coord/inbox & .coord/outbox
|
|
258
|
+
</span>
|
|
259
|
+
</div>
|
|
260
|
+
|
|
261
|
+
{filteredEnvelopes.length === 0 ? (
|
|
262
|
+
<div className="flex flex-col items-center justify-center p-6 rounded-2xl bg-[var(--md-sys-color-surface-container-lowest,#ffffff)] border border-dashed border-[var(--md-sys-color-outline-variant,#cac4d0)] text-xs text-[var(--md-sys-color-outline,#79747e)]">
|
|
263
|
+
<span className="material-symbols-outlined text-2xl mb-1 opacity-50">
|
|
264
|
+
mark_email_read
|
|
265
|
+
</span>
|
|
266
|
+
No pending envelopes for @{selectedPeerId}. Fleet mailbox is synchronized.
|
|
267
|
+
</div>
|
|
268
|
+
) : (
|
|
269
|
+
<div className="flex flex-col gap-2 max-h-60 overflow-y-auto pr-1">
|
|
270
|
+
{filteredEnvelopes.map((env) => {
|
|
271
|
+
const kind = (env.kind in KIND_COLORS ? env.kind : "ping") as EnvelopeKind;
|
|
272
|
+
const style = KIND_COLORS[kind];
|
|
273
|
+
|
|
274
|
+
return (
|
|
275
|
+
<div
|
|
276
|
+
key={env.id}
|
|
277
|
+
className={clsx(
|
|
278
|
+
"flex flex-col gap-1.5 rounded-xl p-3 border text-xs transition-shadow",
|
|
279
|
+
"bg-[var(--md-sys-color-surface-container-low,#f7f2fa)]",
|
|
280
|
+
style.border,
|
|
281
|
+
)}
|
|
282
|
+
>
|
|
283
|
+
<div className="flex items-center justify-between">
|
|
284
|
+
<div className="flex items-center gap-2">
|
|
285
|
+
<span
|
|
286
|
+
className={clsx(
|
|
287
|
+
"rounded-md px-1.5 py-0.5 text-[10px] font-bold uppercase",
|
|
288
|
+
style.bg,
|
|
289
|
+
style.text,
|
|
290
|
+
)}
|
|
291
|
+
>
|
|
292
|
+
{env.kind}
|
|
293
|
+
</span>
|
|
294
|
+
<span className="font-semibold">{env.topic}</span>
|
|
295
|
+
</div>
|
|
296
|
+
<span className="text-[10px] font-mono text-[var(--md-sys-color-outline,#79747e)]">
|
|
297
|
+
{env.from} → {env.to || "all"}
|
|
298
|
+
</span>
|
|
299
|
+
</div>
|
|
300
|
+
|
|
301
|
+
<p className="line-clamp-2 text-xs text-[var(--md-sys-color-on-surface-variant,#49454f)] font-mono whitespace-pre-wrap">
|
|
302
|
+
{env.body}
|
|
303
|
+
</p>
|
|
304
|
+
|
|
305
|
+
<div className="flex items-center justify-between pt-1 border-t border-[var(--md-sys-color-outline-variant,#cac4d0)]/30 text-[10px] text-[var(--md-sys-color-outline,#79747e)] font-mono">
|
|
306
|
+
<span>id: {env.id}</span>
|
|
307
|
+
{env.re && <span>re: {env.re}</span>}
|
|
308
|
+
<span>{new Date(env.ts).toLocaleTimeString()}</span>
|
|
309
|
+
</div>
|
|
310
|
+
</div>
|
|
311
|
+
);
|
|
312
|
+
})}
|
|
313
|
+
</div>
|
|
314
|
+
)}
|
|
315
|
+
</div>
|
|
316
|
+
|
|
317
|
+
{/* Compose Form */}
|
|
318
|
+
<form
|
|
319
|
+
onSubmit={handleSend}
|
|
320
|
+
className="flex flex-col gap-2.5 pt-2 border-t border-[var(--md-sys-color-outline-variant,#cac4d0)]/50"
|
|
321
|
+
>
|
|
322
|
+
<div className="flex items-center gap-2">
|
|
323
|
+
<select
|
|
324
|
+
value={composeKind}
|
|
325
|
+
onChange={(e) => setComposeKind(e.target.value as EnvelopeKind)}
|
|
326
|
+
className="rounded-xl border border-[var(--md-sys-color-outline-variant,#cac4d0)] bg-[var(--md-sys-color-surface-container-low,#f7f2fa)] px-2.5 py-1.5 text-xs font-medium text-[var(--md-sys-color-on-surface,#1d1b20)] focus:outline-none focus:ring-1 focus:ring-[var(--md-sys-color-primary,#6750a4)]"
|
|
327
|
+
>
|
|
328
|
+
<option value="ask">ASK</option>
|
|
329
|
+
<option value="fact">FACT</option>
|
|
330
|
+
<option value="ack">ACK</option>
|
|
331
|
+
<option value="ping">PING</option>
|
|
332
|
+
<option value="error">ERROR</option>
|
|
333
|
+
</select>
|
|
334
|
+
|
|
335
|
+
<input
|
|
336
|
+
type="text"
|
|
337
|
+
placeholder="Topic (e.g. work, claim-check, verification)..."
|
|
338
|
+
value={composeTopic}
|
|
339
|
+
onChange={(e) => setComposeTopic(e.target.value)}
|
|
340
|
+
className="flex-1 rounded-xl border border-[var(--md-sys-color-outline-variant,#cac4d0)] bg-[var(--md-sys-color-surface-container-low,#f7f2fa)] px-3 py-1.5 text-xs text-[var(--md-sys-color-on-surface,#1d1b20)] placeholder-[var(--md-sys-color-outline,#79747e)] focus:outline-none focus:ring-1 focus:ring-[var(--md-sys-color-primary,#6750a4)]"
|
|
341
|
+
/>
|
|
342
|
+
</div>
|
|
343
|
+
|
|
344
|
+
<div className="flex items-center gap-2">
|
|
345
|
+
<input
|
|
346
|
+
type="text"
|
|
347
|
+
placeholder={`Message body to @${selectedPeerId}...`}
|
|
348
|
+
value={composeBody}
|
|
349
|
+
onChange={(e) => setComposeBody(e.target.value)}
|
|
350
|
+
className="flex-1 rounded-xl border border-[var(--md-sys-color-outline-variant,#cac4d0)] bg-[var(--md-sys-color-surface-container-low,#f7f2fa)] px-3 py-1.5 text-xs text-[var(--md-sys-color-on-surface,#1d1b20)] placeholder-[var(--md-sys-color-outline,#79747e)] focus:outline-none focus:ring-1 focus:ring-[var(--md-sys-color-primary,#6750a4)]"
|
|
351
|
+
/>
|
|
352
|
+
|
|
353
|
+
<button
|
|
354
|
+
type="submit"
|
|
355
|
+
disabled={!composeTopic.trim() || !composeBody.trim()}
|
|
356
|
+
className={clsx(
|
|
357
|
+
"flex items-center gap-1.5 rounded-xl px-4 py-1.5 text-xs font-semibold transition-all",
|
|
358
|
+
composeTopic.trim() && composeBody.trim()
|
|
359
|
+
? "bg-[var(--md-sys-color-primary,#6750a4)] text-[var(--md-sys-color-on-primary,#ffffff)] hover:opacity-90 shadow-sm"
|
|
360
|
+
: "bg-[var(--md-sys-color-surface-container-high,#ece6f0)] text-[var(--md-sys-color-outline,#79747e)] cursor-not-allowed",
|
|
361
|
+
)}
|
|
362
|
+
>
|
|
363
|
+
<span>Dispatch</span>
|
|
364
|
+
<span className="material-symbols-outlined text-sm">send</span>
|
|
365
|
+
</button>
|
|
366
|
+
</div>
|
|
367
|
+
</form>
|
|
368
|
+
</div>
|
|
369
|
+
);
|
|
370
|
+
};
|
|
@@ -0,0 +1,218 @@
|
|
|
1
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
2
|
+
/**
|
|
3
|
+
* @aphrody/m3-ai - A2ATaskTimeline
|
|
4
|
+
*
|
|
5
|
+
* Material Design 3 Agent-to-Agent Delegation Timeline:
|
|
6
|
+
* - Chronological trace of task handoffs between agents (e.g., user -> agycli -> codex -> claude -> rag).
|
|
7
|
+
* - Displays delegation phase, envelope kind, file claims, and execution status.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import React, { useId, useState } from "react";
|
|
11
|
+
import { clsx } from "clsx";
|
|
12
|
+
import type { CoordEnvelope } from "./AgUiEventStream";
|
|
13
|
+
|
|
14
|
+
export interface TimelineStep {
|
|
15
|
+
id: string;
|
|
16
|
+
from: string;
|
|
17
|
+
to: string;
|
|
18
|
+
title: string;
|
|
19
|
+
description?: string;
|
|
20
|
+
status: "pending" | "running" | "completed" | "error";
|
|
21
|
+
timestamp: string;
|
|
22
|
+
envelope?: Partial<CoordEnvelope>;
|
|
23
|
+
claim?: string;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export interface A2ATaskTimelineProps {
|
|
27
|
+
steps?: TimelineStep[];
|
|
28
|
+
currentStepId?: string;
|
|
29
|
+
onSelectStep?: (step: TimelineStep) => void;
|
|
30
|
+
className?: string;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
const DEFAULT_TIMELINE_STEPS: TimelineStep[] = [
|
|
34
|
+
{
|
|
35
|
+
id: "step-agy-lead",
|
|
36
|
+
from: "user",
|
|
37
|
+
to: "agy",
|
|
38
|
+
title: "Lead Coordination & Monorepo Integration",
|
|
39
|
+
status: "completed",
|
|
40
|
+
timestamp: "Live",
|
|
41
|
+
description: "Coordination active via .coord mailbox and OS resource claims.",
|
|
42
|
+
},
|
|
43
|
+
{
|
|
44
|
+
id: "step-workers",
|
|
45
|
+
from: "agy",
|
|
46
|
+
to: "codex",
|
|
47
|
+
title: "Subagent / Peer Parallel Tasks",
|
|
48
|
+
status: "running",
|
|
49
|
+
timestamp: "Ongoing",
|
|
50
|
+
description: "Workers executing disjoint task families under cooperatively acquired locks.",
|
|
51
|
+
},
|
|
52
|
+
];
|
|
53
|
+
|
|
54
|
+
export const A2ATaskTimeline: React.FC<A2ATaskTimelineProps> = ({
|
|
55
|
+
steps = DEFAULT_TIMELINE_STEPS,
|
|
56
|
+
currentStepId,
|
|
57
|
+
onSelectStep,
|
|
58
|
+
className,
|
|
59
|
+
}) => {
|
|
60
|
+
const [expandedStepId, setExpandedStepId] = useState<string | null>(null);
|
|
61
|
+
const timelineId = useId();
|
|
62
|
+
|
|
63
|
+
const toggleExpand = (id: string) => {
|
|
64
|
+
setExpandedStepId((current) => (current === id ? null : id));
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
return (
|
|
68
|
+
<div
|
|
69
|
+
className={clsx(
|
|
70
|
+
"flex flex-col gap-3 rounded-3xl p-5 border",
|
|
71
|
+
"bg-[var(--md-sys-color-surface-container-lowest,#ffffff)]",
|
|
72
|
+
"border-[var(--md-sys-color-outline-variant,#cac4d0)]",
|
|
73
|
+
"text-[var(--md-sys-color-on-surface,#1d1b20)]",
|
|
74
|
+
className,
|
|
75
|
+
)}
|
|
76
|
+
>
|
|
77
|
+
<div className="flex items-center justify-between border-b border-[var(--md-sys-color-outline-variant,#cac4d0)]/40 pb-3">
|
|
78
|
+
<div className="flex items-center gap-2">
|
|
79
|
+
<span className="material-symbols-outlined text-lg text-[var(--md-sys-color-primary,#6750a4)]">
|
|
80
|
+
timeline
|
|
81
|
+
</span>
|
|
82
|
+
<h3 className="text-sm font-semibold tracking-tight">A2A Task Delegation Timeline</h3>
|
|
83
|
+
</div>
|
|
84
|
+
<span className="text-xs text-[var(--md-sys-color-outline,#79747e)]">
|
|
85
|
+
{steps.length} delegation steps
|
|
86
|
+
</span>
|
|
87
|
+
</div>
|
|
88
|
+
|
|
89
|
+
<div className="relative flex flex-col gap-4 pl-4 pt-1">
|
|
90
|
+
{/* Continuous connector line */}
|
|
91
|
+
<div className="absolute left-[27px] top-4 bottom-4 w-0.5 bg-[var(--md-sys-color-outline-variant,#cac4d0)]/60" />
|
|
92
|
+
|
|
93
|
+
{steps.map((step) => {
|
|
94
|
+
const isCurrent = step.id === currentStepId;
|
|
95
|
+
const isExpanded = step.id === expandedStepId;
|
|
96
|
+
const detailsId = `${timelineId}-${encodeURIComponent(step.id)}`;
|
|
97
|
+
const date = new Date(step.timestamp);
|
|
98
|
+
const time = Number.isNaN(date.getTime()) ? step.timestamp : date.toLocaleTimeString();
|
|
99
|
+
|
|
100
|
+
const statusIcon =
|
|
101
|
+
step.status === "completed"
|
|
102
|
+
? "check_circle"
|
|
103
|
+
: step.status === "running"
|
|
104
|
+
? "sync"
|
|
105
|
+
: step.status === "error"
|
|
106
|
+
? "error"
|
|
107
|
+
: "radio_button_unchecked";
|
|
108
|
+
|
|
109
|
+
const statusColor =
|
|
110
|
+
step.status === "completed"
|
|
111
|
+
? "text-emerald-500"
|
|
112
|
+
: step.status === "running"
|
|
113
|
+
? "text-[var(--md-sys-color-primary,#6750a4)] animate-spin motion-reduce:animate-none"
|
|
114
|
+
: step.status === "error"
|
|
115
|
+
? "text-rose-500"
|
|
116
|
+
: "text-slate-400";
|
|
117
|
+
|
|
118
|
+
return (
|
|
119
|
+
<div
|
|
120
|
+
key={step.id}
|
|
121
|
+
className={clsx(
|
|
122
|
+
"relative flex items-start gap-3 transition-all",
|
|
123
|
+
isCurrent && "font-medium",
|
|
124
|
+
)}
|
|
125
|
+
>
|
|
126
|
+
{/* Step indicator node */}
|
|
127
|
+
<div
|
|
128
|
+
className={clsx(
|
|
129
|
+
"relative z-10 flex h-7 w-7 items-center justify-center rounded-full border-2 bg-[var(--md-sys-color-surface-container-lowest,#ffffff)]",
|
|
130
|
+
step.status === "completed"
|
|
131
|
+
? "border-emerald-500"
|
|
132
|
+
: step.status === "running"
|
|
133
|
+
? "border-[var(--md-sys-color-primary,#6750a4)] ring-4 ring-[var(--md-sys-color-primary,#6750a4)]/20"
|
|
134
|
+
: step.status === "error"
|
|
135
|
+
? "border-rose-500"
|
|
136
|
+
: "border-[var(--md-sys-color-outline-variant,#cac4d0)]",
|
|
137
|
+
)}
|
|
138
|
+
>
|
|
139
|
+
<span className={clsx("material-symbols-outlined text-sm", statusColor)}>
|
|
140
|
+
{statusIcon}
|
|
141
|
+
</span>
|
|
142
|
+
</div>
|
|
143
|
+
|
|
144
|
+
{/* Step details card */}
|
|
145
|
+
<div
|
|
146
|
+
className={clsx(
|
|
147
|
+
"flex-1 cursor-pointer rounded-2xl border p-3 text-xs transition-all",
|
|
148
|
+
"bg-[var(--md-sys-color-surface-container-low,#f7f2fa)] hover:bg-[var(--md-sys-color-surface-container,#f3edf7)]",
|
|
149
|
+
isCurrent
|
|
150
|
+
? "border-[var(--md-sys-color-primary,#6750a4)] shadow-sm"
|
|
151
|
+
: "border-[var(--md-sys-color-outline-variant,#cac4d0)]/60",
|
|
152
|
+
)}
|
|
153
|
+
>
|
|
154
|
+
<button
|
|
155
|
+
type="button"
|
|
156
|
+
aria-expanded={step.envelope ? isExpanded : undefined}
|
|
157
|
+
aria-controls={step.envelope ? detailsId : undefined}
|
|
158
|
+
onClick={() => {
|
|
159
|
+
toggleExpand(step.id);
|
|
160
|
+
onSelectStep?.(step);
|
|
161
|
+
}}
|
|
162
|
+
className="block w-full rounded-medium text-left outline-none focus-visible:ring-2 focus-visible:ring-primary"
|
|
163
|
+
>
|
|
164
|
+
<span className="flex items-center justify-between gap-2">
|
|
165
|
+
<span className="flex items-center gap-1.5">
|
|
166
|
+
<span className="font-semibold text-xs text-[var(--md-sys-color-on-surface,#1d1b20)]">
|
|
167
|
+
{step.title}
|
|
168
|
+
</span>
|
|
169
|
+
<span className="rounded-md bg-[var(--md-sys-color-surface-container-highest,#e6e0e9)] px-1.5 py-0.2 text-[10px] font-mono text-[var(--md-sys-color-on-surface-variant,#49454f)]">
|
|
170
|
+
@{step.from} → @{step.to}
|
|
171
|
+
</span>
|
|
172
|
+
</span>
|
|
173
|
+
<span className="text-[10px] font-mono text-[var(--md-sys-color-outline,#79747e)]">
|
|
174
|
+
{time}
|
|
175
|
+
</span>
|
|
176
|
+
</span>
|
|
177
|
+
|
|
178
|
+
{step.description && (
|
|
179
|
+
<span className="mt-1 block text-xs text-[var(--md-sys-color-on-surface-variant,#49454f)]">
|
|
180
|
+
{step.description}
|
|
181
|
+
</span>
|
|
182
|
+
)}
|
|
183
|
+
|
|
184
|
+
{step.claim && (
|
|
185
|
+
<span className="mt-2 flex items-center gap-1.5 text-[11px] font-mono text-amber-700 dark:text-amber-400">
|
|
186
|
+
<span className="material-symbols-outlined text-xs">lock</span>
|
|
187
|
+
<span>claim: {step.claim}</span>
|
|
188
|
+
</span>
|
|
189
|
+
)}
|
|
190
|
+
</button>
|
|
191
|
+
|
|
192
|
+
{/* Expanded envelope payload view */}
|
|
193
|
+
{step.envelope && (
|
|
194
|
+
<div
|
|
195
|
+
id={detailsId}
|
|
196
|
+
hidden={!isExpanded}
|
|
197
|
+
className="mt-2.5 pt-2 border-t border-[var(--md-sys-color-outline-variant,#cac4d0)]/40 font-mono text-[11px] text-[var(--md-sys-color-on-surface-variant,#49454f)]"
|
|
198
|
+
>
|
|
199
|
+
<div className="flex items-center gap-2 mb-1">
|
|
200
|
+
<span className="font-semibold">Kind: {step.envelope.kind}</span>
|
|
201
|
+
<span>·</span>
|
|
202
|
+
<span>Topic: {step.envelope.topic}</span>
|
|
203
|
+
</div>
|
|
204
|
+
{step.envelope.body && (
|
|
205
|
+
<pre className="rounded-lg bg-[var(--md-sys-color-surface-container-highest,#e6e0e9)]/50 p-2 overflow-x-auto whitespace-pre-wrap">
|
|
206
|
+
{step.envelope.body}
|
|
207
|
+
</pre>
|
|
208
|
+
)}
|
|
209
|
+
</div>
|
|
210
|
+
)}
|
|
211
|
+
</div>
|
|
212
|
+
</div>
|
|
213
|
+
);
|
|
214
|
+
})}
|
|
215
|
+
</div>
|
|
216
|
+
</div>
|
|
217
|
+
);
|
|
218
|
+
};
|